aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server
diff options
context:
space:
mode:
authorNassim Jahnke <[email protected]>2024-12-03 19:54:10 +0100
committerNassim Jahnke <[email protected]>2024-12-03 20:00:08 +0100
commitbd55e322d8cfb8f4d4fed04b38243bc4940fab50 (patch)
treec417bfb8a8727f6bc27e59586fb507b48bff51a2 /patches/server
parentc60e47fa58c171befcfb4ca48f38a201d559f90b (diff)
downloadPaper-bd55e322d8cfb8f4d4fed04b38243bc4940fab50.tar.gz
Paper-bd55e322d8cfb8f4d4fed04b38243bc4940fab50.zip
More more more work
Diffstat (limited to 'patches/server')
-rw-r--r--patches/server/0263-Don-t-allow-digging-into-unloaded-chunks.patch14
-rw-r--r--patches/server/0267-Book-size-limits.patch2
-rw-r--r--patches/server/0274-Brigadier-Mojang-API.patch2
-rw-r--r--patches/server/0275-Limit-Client-Sign-length-more.patch2
-rw-r--r--patches/server/0347-Prevent-teleporting-dead-entities.patch2
-rw-r--r--patches/server/0362-Prevent-position-desync-causing-tp-exploit.patch2
-rw-r--r--patches/server/0364-Add-PlayerRecipeBookClickEvent.patch2
-rw-r--r--patches/server/0366-Add-permission-for-command-blocks.patch2
-rw-r--r--patches/server/0368-Fix-Per-World-Difficulty-Remembering-Difficulty.patch2
-rw-r--r--patches/server/0373-Do-not-accept-invalid-client-settings.patch2
-rw-r--r--patches/server/0401-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch2
-rw-r--r--patches/server/0411-Fix-for-large-move-vectors-crashing-server.patch6
-rw-r--r--patches/server/0435-Limit-recipe-packets.patch2
-rw-r--r--patches/server/0446-Allow-disabling-mob-spawner-spawn-egg-transformation.patch19
-rw-r--r--patches/server/0447-Fix-Not-a-string-Map-Conversion-spam.patch45
-rw-r--r--patches/server/0448-Add-PlayerFlowerPotManipulateEvent.patch48
-rw-r--r--patches/server/0449-Fix-interact-event-not-being-called-sometimes.patch48
-rw-r--r--patches/server/0450-Zombie-API-breaking-doors.patch24
-rw-r--r--patches/server/0451-Fix-nerfed-slime-when-splitting.patch18
-rw-r--r--patches/server/0452-Add-EntityLoadCrossbowEvent.patch68
-rw-r--r--patches/server/0453-Add-WorldGameRuleChangeEvent.patch100
-rw-r--r--patches/server/0454-Add-ServerResourcesReloadedEvent.patch54
-rw-r--r--patches/server/0455-Add-world-settings-for-mobs-picking-up-loot.patch32
-rw-r--r--patches/server/0456-Add-BlockFailedDispenseEvent.patch50
-rw-r--r--patches/server/0457-Add-PlayerLecternPageChangeEvent.patch46
-rw-r--r--patches/server/0458-Add-PlayerLoomPatternSelectEvent.patch45
-rw-r--r--patches/server/0459-Configurable-door-breaking-difficulty.patch37
-rw-r--r--patches/server/0460-Empty-commands-shall-not-be-dispatched.patch18
-rw-r--r--patches/server/0461-Remove-stale-POIs.patch22
-rw-r--r--patches/server/0462-Fix-villager-boat-exploit.patch25
-rw-r--r--patches/server/0463-Add-sendOpLevel-API.patch53
-rw-r--r--patches/server/0464-Add-RegistryAccess-for-managing-Registries.patch1405
-rw-r--r--patches/server/0465-Add-StructuresLocateEvent.patch36
-rw-r--r--patches/server/0466-Collision-option-for-requiring-a-player-participant.patch42
-rw-r--r--patches/server/0467-Return-chat-component-with-empty-text-instead-of-thr.patch25
-rw-r--r--patches/server/0468-Make-schedule-command-per-world.patch28
-rw-r--r--patches/server/0469-Configurable-max-leash-distance.patch32
-rw-r--r--patches/server/0470-Add-BlockPreDispenseEvent.patch46
-rw-r--r--patches/server/0471-Add-PlayerChangeBeaconEffectEvent.patch38
-rw-r--r--patches/server/0472-Add-toggle-for-always-placing-the-dragon-egg.patch19
-rw-r--r--patches/server/0473-Add-PlayerStonecutterRecipeSelectEvent.patch57
-rw-r--r--patches/server/0474-Expand-EntityUnleashEvent.patch174
-rw-r--r--patches/server/0475-Reset-shield-blocking-on-dimension-change.patch22
-rw-r--r--patches/server/0476-Add-DragonEggFormEvent.patch34
-rw-r--r--patches/server/0477-Add-EntityMoveEvent.patch55
-rw-r--r--patches/server/0478-added-option-to-disable-pathfinding-updates-on-block.patch26
-rw-r--r--patches/server/0479-Inline-shift-direction-fields.patch56
-rw-r--r--patches/server/0480-Allow-adding-items-to-BlockDropItemEvent.patch44
-rw-r--r--patches/server/0481-Add-getMainThreadExecutor-to-BukkitScheduler.patch26
-rw-r--r--patches/server/0482-living-entity-allow-attribute-registration.patch57
-rw-r--r--patches/server/0483-fix-dead-slime-setSize-invincibility.patch19
-rw-r--r--patches/server/0484-Merchant-getRecipes-should-return-an-immutable-list.patch19
-rw-r--r--patches/server/0485-Expose-Tracked-Players.patch32
-rw-r--r--patches/server/0486-Improve-ServerGUI.patch431
-rw-r--r--patches/server/0487-fix-converting-txt-to-json-file.patch61
-rw-r--r--patches/server/0488-Add-worldborder-events.patch72
-rw-r--r--patches/server/0489-Add-PlayerNameEntityEvent.patch26
-rw-r--r--patches/server/0490-Add-recipe-to-cook-events.patch44
-rw-r--r--patches/server/0491-Add-Block-isValidTool.patch20
-rw-r--r--patches/server/0492-Allow-using-signs-inside-spawn-protection.patch19
-rw-r--r--patches/server/0493-Expand-world-key-API.patch84
-rw-r--r--patches/server/0494-Add-fast-alternative-constructor-for-Rotations.patch29
-rw-r--r--patches/server/0495-Drop-carried-item-when-player-has-disconnected.patch27
-rw-r--r--patches/server/0496-forced-whitelist-use-configurable-kick-message.patch19
-rw-r--r--patches/server/0497-Don-t-ignore-result-of-PlayerEditBookEvent.patch19
-rw-r--r--patches/server/0498-Expose-protocol-version.patch22
-rw-r--r--patches/server/0499-Enhance-console-tab-completions-for-brigadier-comman.patch445
-rw-r--r--patches/server/0500-Fix-PlayerItemConsumeEvent-cancelling-properly.patch22
-rw-r--r--patches/server/0501-Add-bypass-host-check.patch30
-rw-r--r--patches/server/0502-Set-area-affect-cloud-rotation.patch19
-rw-r--r--patches/server/0503-add-isDeeplySleeping-to-HumanEntity.patch24
-rw-r--r--patches/server/0504-add-consumeFuel-to-FurnaceBurnEvent.patch19
-rw-r--r--patches/server/0505-add-get-set-drop-chance-to-EntityEquipment.patch72
-rw-r--r--patches/server/0506-fix-PigZombieAngerEvent-cancellation.patch35
-rw-r--r--patches/server/0507-fix-PlayerItemHeldEvent-firing-twice.patch18
-rw-r--r--patches/server/0508-Add-PlayerDeepSleepEvent.patch24
-rw-r--r--patches/server/0509-More-World-API.patch57
-rw-r--r--patches/server/0510-Add-PlayerBedFailEnterEvent.patch36
-rw-r--r--patches/server/0511-Implement-methods-to-convert-between-Component-and-B.patch55
-rw-r--r--patches/server/0512-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch26
-rw-r--r--patches/server/0513-Introduce-beacon-activation-deactivation-events.patch37
-rw-r--r--patches/server/0514-Add-Channel-initialization-listeners.patch155
-rw-r--r--patches/server/0515-Send-empty-commands-if-tab-completion-is-disabled.patch24
-rw-r--r--patches/server/0516-Add-more-WanderingTrader-API.patch65
-rw-r--r--patches/server/0517-Add-EntityBlockStorage-clearEntities.patch38
-rw-r--r--patches/server/0518-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch35
-rw-r--r--patches/server/0519-Add-HiddenPotionEffect-API.patch29
-rw-r--r--patches/server/0520-Inventory-close.patch25
-rw-r--r--patches/server/0521-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch130
-rw-r--r--patches/server/0522-Add-basic-Datapack-API.patch209
-rw-r--r--patches/server/0523-Add-environment-variable-to-disable-server-gui.patch18
-rw-r--r--patches/server/0524-Expand-PlayerGameModeChangeEvent.patch161
-rw-r--r--patches/server/0525-ItemStack-repair-check-API.patch72
-rw-r--r--patches/server/0526-More-Enchantment-API.patch105
-rw-r--r--patches/server/0527-Move-range-check-for-block-placing-up.patch22
-rw-r--r--patches/server/0528-Add-Mob-lookAt-API.patch64
-rw-r--r--patches/server/0529-Correctly-check-if-bucket-dispenses-will-succeed-for.patch28
-rw-r--r--patches/server/0530-Add-Unix-domain-socket-support.patch137
-rw-r--r--patches/server/0531-Add-EntityInsideBlockEvent.patch294
-rw-r--r--patches/server/0532-Improve-item-default-attribute-API.patch80
-rw-r--r--patches/server/0533-Add-cause-to-Weather-ThunderChangeEvents.patch118
-rw-r--r--patches/server/0534-More-Lidded-Block-API.patch79
-rw-r--r--patches/server/0535-Limit-item-frame-cursors-on-maps.patch30
-rw-r--r--patches/server/0536-Add-PlayerKickEvent-causes.patch548
-rw-r--r--patches/server/0537-Add-PufferFishStateChangeEvent.patch50
-rw-r--r--patches/server/0538-Fix-PlayerBucketEmptyEvent-result-itemstack.patch42
-rw-r--r--patches/server/0539-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch91
-rw-r--r--patches/server/0540-Add-option-to-fix-items-merging-through-walls.patch25
-rw-r--r--patches/server/0541-Add-BellRevealRaiderEvent.patch33
-rw-r--r--patches/server/0542-Fix-invulnerable-end-crystals.patch65
-rw-r--r--patches/server/0543-Add-ElderGuardianAppearanceEvent.patch48
-rw-r--r--patches/server/0544-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch53
-rw-r--r--patches/server/0545-Line-Of-Sight-Changes.patch74
-rw-r--r--patches/server/0546-add-per-world-spawn-limits.patch24
-rw-r--r--patches/server/0547-Fix-potions-splash-events.patch181
-rw-r--r--patches/server/0548-Add-more-LimitedRegion-API.patch56
-rw-r--r--patches/server/0549-Fix-PlayerDropItemEvent-using-wrong-item.patch57
-rw-r--r--patches/server/0550-Missing-Entity-API.patch1402
-rw-r--r--patches/server/0551-Fix-return-value-of-Block-applyBoneMeal-always-being.patch19
-rw-r--r--patches/server/0552-Use-getChunkIfLoadedImmediately-in-places.patch53
-rw-r--r--patches/server/0553-Fix-commands-from-signs-not-firing-command-events.patch121
-rw-r--r--patches/server/0554-Add-PlayerArmSwingEvent.patch19
-rw-r--r--patches/server/0555-Fix-kick-event-leave-message-not-being-sent.patch127
-rw-r--r--patches/server/0556-Don-t-apply-cramming-damage-to-players.patch25
-rw-r--r--patches/server/0557-Rate-options-and-timings-for-sensors-and-behaviors.patch88
-rw-r--r--patches/server/0558-Add-missing-forceDrop-toggles.patch124
-rw-r--r--patches/server/0559-Stinger-API.patch50
-rw-r--r--patches/server/0560-Add-System.out-err-catcher.patch118
-rw-r--r--patches/server/0561-Prevent-AFK-kick-while-watching-end-credits.patch19
-rw-r--r--patches/server/0562-Allow-skipping-writing-of-comments-to-server.propert.patch69
-rw-r--r--patches/server/0563-Add-PlayerSetSpawnEvent.patch204
-rw-r--r--patches/server/0564-Make-hoppers-respect-inventory-max-stack-size.patch30
-rw-r--r--patches/server/0565-Optimize-entity-tracker-passenger-checks.patch19
-rw-r--r--patches/server/0566-Config-option-for-Piglins-guarding-chests.patch18
-rw-r--r--patches/server/0567-Add-EntityDamageItemEvent.patch94
-rw-r--r--patches/server/0568-Optimize-indirect-passenger-iteration.patch53
-rw-r--r--patches/server/0569-Configurable-item-frame-map-cursor-update-interval.patch19
-rw-r--r--patches/server/0570-Change-EnderEye-target-without-changing-other-things.patch54
-rw-r--r--patches/server/0571-Add-BlockBreakBlockEvent.patch87
-rw-r--r--patches/server/0572-Option-to-prevent-data-components-copy-in-smithing-r.patch157
-rw-r--r--patches/server/0573-More-CommandBlock-API.patch101
-rw-r--r--patches/server/0574-Add-missing-team-sidebar-display-slots.patch114
-rw-r--r--patches/server/0575-Add-back-EntityPortalExitEvent.patch48
-rw-r--r--patches/server/0576-Add-methods-to-find-targets-for-lightning-strikes.patch60
-rw-r--r--patches/server/0577-Get-entity-default-attributes.patch151
-rw-r--r--patches/server/0578-Left-handed-API.patch27
-rw-r--r--patches/server/0579-Add-more-advancement-API.patch213
-rw-r--r--patches/server/0580-Add-ItemFactory-getSpawnEgg-API.patch58
-rw-r--r--patches/server/0581-Add-critical-damage-API.patch101
-rw-r--r--patches/server/0582-Fix-issues-with-mob-conversion.patch74
-rw-r--r--patches/server/0583-Add-hasCollision-methods-to-various-places.patch56
-rw-r--r--patches/server/0584-Goat-ram-API.patch42
-rw-r--r--patches/server/0585-Add-API-for-resetting-a-single-score.patch24
-rw-r--r--patches/server/0586-Add-Raw-Byte-Entity-Serialization.patch90
-rw-r--r--patches/server/0587-Vanilla-command-permission-fixes.patch79
-rw-r--r--patches/server/0588-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch68
-rw-r--r--patches/server/0589-Fix-GameProfileCache-concurrency.patch126
-rw-r--r--patches/server/0590-Improve-and-expand-AsyncCatcher.patch227
-rw-r--r--patches/server/0591-Add-paper-mobcaps-and-paper-playermobcaps.patch343
-rw-r--r--patches/server/0592-Sanitize-ResourceLocation-error-logging.patch28
-rw-r--r--patches/server/0593-Manually-inline-methods-in-BlockPosition.patch63
-rw-r--r--patches/server/0594-Name-craft-scheduler-threads-according-to-the-plugin.patch33
-rw-r--r--patches/server/0595-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch31
-rw-r--r--patches/server/0596-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch21
-rw-r--r--patches/server/0597-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch26
-rw-r--r--patches/server/0598-Oprimise-map-impl-for-tracked-players.patch21
-rw-r--r--patches/server/0599-Add-missing-InventoryType.patch22
-rw-r--r--patches/server/0600-Optimise-BlockSoil-nearby-water-lookup.patch52
-rw-r--r--patches/server/0601-Fix-merchant-inventory-not-closing-on-entity-removal.patch22
-rw-r--r--patches/server/0602-Check-requirement-before-suggesting-root-nodes.patch32
-rw-r--r--patches/server/0603-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch23
-rw-r--r--patches/server/0604-Add-packet-limiter-config.patch108
-rw-r--r--patches/server/0605-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch72
-rw-r--r--patches/server/0606-Ensure-valid-vehicle-status.patch19
-rw-r--r--patches/server/0607-Prevent-softlocked-end-exit-portal-generation.patch22
-rw-r--r--patches/server/0608-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch19
-rw-r--r--patches/server/0609-Don-t-log-debug-logging-being-disabled.patch19
-rw-r--r--patches/server/0610-fix-various-menus-with-empty-level-accesses.patch23
-rw-r--r--patches/server/0611-Preserve-overstacked-loot.patch27
-rw-r--r--patches/server/0612-Update-head-rotation-in-missing-places.patch29
-rw-r--r--patches/server/0613-prevent-unintended-light-block-manipulation.patch25
-rw-r--r--patches/server/0614-Fix-CraftCriteria-defaults-map.patch19
-rw-r--r--patches/server/0615-Fix-upstreams-block-state-factories.patch491
-rw-r--r--patches/server/0616-Configurable-feature-seeds.patch27
-rw-r--r--patches/server/0617-Add-root-admin-user-detection.patch62
-rw-r--r--patches/server/0618-don-t-attempt-to-teleport-dead-entities.patch19
-rw-r--r--patches/server/0619-Prevent-excessive-velocity-through-repeated-crits.patch41
-rw-r--r--patches/server/0620-Remove-client-side-code-using-deprecated-for-removal.patch30
-rw-r--r--patches/server/0621-Fix-Spigot-growth-modifiers.patch141
-rw-r--r--patches/server/0622-Prevent-ContainerOpenersCounter-openCount-from-going.patch18
-rw-r--r--patches/server/0623-Add-PlayerItemFrameChangeEvent.patch61
-rw-r--r--patches/server/0624-Optimize-HashMapPalette.patch57
-rw-r--r--patches/server/0625-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch44
-rw-r--r--patches/server/0626-Add-more-Campfire-API.patch111
-rw-r--r--patches/server/0627-Forward-CraftEntity-in-teleport-command.patch35
-rw-r--r--patches/server/0628-Improve-scoreboard-entries.patch88
-rw-r--r--patches/server/0629-Entity-powdered-snow-API.patch42
-rw-r--r--patches/server/0630-Add-API-for-item-entity-health.patch34
-rw-r--r--patches/server/0631-Configurable-max-block-light-for-monster-spawning.patch19
-rw-r--r--patches/server/0632-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch85
-rw-r--r--patches/server/0633-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch32
-rw-r--r--patches/server/0634-Bucketable-API.patch69
-rw-r--r--patches/server/0635-Validate-usernames.patch76
-rw-r--r--patches/server/0636-Make-water-animal-spawn-height-configurable.patch21
-rw-r--r--patches/server/0637-Expose-vanilla-BiomeProvider-from-WorldInfo.patch177
-rw-r--r--patches/server/0638-Add-config-option-for-worlds-affected-by-time-cmd.patch28
-rw-r--r--patches/server/0639-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch18
-rw-r--r--patches/server/0640-Multiple-Entries-with-Scoreboards.patch125
-rw-r--r--patches/server/0641-Reset-placed-block-on-exception.patch39
-rw-r--r--patches/server/0642-Add-configurable-height-for-slime-spawn.patch35
-rw-r--r--patches/server/0643-Fix-xp-reward-for-baby-zombies.patch32
-rw-r--r--patches/server/0644-Multi-Block-Change-API-Implementation.patch62
-rw-r--r--patches/server/0645-Fix-NotePlayEvent.patch54
-rw-r--r--patches/server/0646-Freeze-Tick-Lock-API.patch82
-rw-r--r--patches/server/0647-More-PotionEffectType-API.patch98
-rw-r--r--patches/server/0648-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch20
-rw-r--r--patches/server/0649-API-for-creating-command-sender-which-forwards-feedb.patch170
-rw-r--r--patches/server/0650-Add-missing-structure-set-seed-configs.patch399
-rw-r--r--patches/server/0651-Fix-cancelled-powdered-snow-bucket-placement.patch31
-rw-r--r--patches/server/0652-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch20
-rw-r--r--patches/server/0653-Add-GameEvent-tags.patch81
-rw-r--r--patches/server/0654-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch37
-rw-r--r--patches/server/0655-Furnace-RecipesUsed-API.patch48
-rw-r--r--patches/server/0656-Configurable-sculk-sensor-listener-range.patch106
-rw-r--r--patches/server/0657-Add-missing-block-data-API.patch214
-rw-r--r--patches/server/0658-Option-to-have-default-CustomSpawners-in-custom-worl.patch32
-rw-r--r--patches/server/0659-Put-world-into-worldlist-before-initing-the-world.patch41
-rw-r--r--patches/server/0660-Custom-Potion-Mixes.patch329
-rw-r--r--patches/server/0661-Force-close-world-loading-screen.patch32
-rw-r--r--patches/server/0662-Fix-falling-block-spawn-methods.patch57
-rw-r--r--patches/server/0663-Expose-furnace-minecart-push-values.patch42
-rw-r--r--patches/server/0664-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch40
-rw-r--r--patches/server/0665-More-Projectile-API.patch932
-rw-r--r--patches/server/0666-Fix-swamp-hut-cat-generation-deadlock.patch64
-rw-r--r--patches/server/0667-Don-t-allow-vehicle-movement-from-players-while-tele.patch24
-rw-r--r--patches/server/0668-Implement-getComputedBiome-API.patch61
-rw-r--r--patches/server/0669-Make-some-itemstacks-nonnull.patch28
-rw-r--r--patches/server/0670-Implement-enchantWithLevels-API.patch58
-rw-r--r--patches/server/0671-Fix-saving-in-unloadWorld.patch20
-rw-r--r--patches/server/0672-Buffer-OOB-setBlock-calls.patch42
-rw-r--r--patches/server/0673-Add-TameableDeathMessageEvent.patch24
-rw-r--r--patches/server/0674-Fix-new-block-data-for-EntityChangeBlockEvent.patch215
-rw-r--r--patches/server/0675-fix-player-loottables-running-when-mob-loot-gamerule.patch25
-rw-r--r--patches/server/0676-Ensure-entity-passenger-world-matches-ridden-entity.patch20
-rw-r--r--patches/server/0677-Cache-resource-keys-and-optimize-reference-Holder-ta.patch38
-rw-r--r--patches/server/0678-Allow-changing-the-EnderDragon-podium.patch142
-rw-r--r--patches/server/0679-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch40
-rw-r--r--patches/server/0680-Use-username-instead-of-display-name-in-PlayerList-g.patch20
-rw-r--r--patches/server/0681-Expand-PlayerItemDamageEvent.patch23
-rw-r--r--patches/server/0682-WorldCreator-keepSpawnLoaded.patch19
-rw-r--r--patches/server/0683-Fix-CME-in-CraftPersistentDataTypeRegistry.patch19
-rw-r--r--patches/server/0684-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch54
-rw-r--r--patches/server/0685-Add-EntityDyeEvent-and-CollarColorable-interface.patch43
-rw-r--r--patches/server/0686-Fire-CauldronLevelChange-on-initial-fill.patch122
-rw-r--r--patches/server/0687-fix-powder-snow-cauldrons-not-turning-to-water.patch45
-rw-r--r--patches/server/0688-Add-PlayerStopUsingItemEvent.patch18
-rw-r--r--patches/server/0689-Don-t-tick-markers.patch36
-rw-r--r--patches/server/0690-Expand-FallingBlock-API.patch107
-rw-r--r--patches/server/0691-Add-support-for-Proxy-Protocol.patch65
-rw-r--r--patches/server/0692-Fix-OfflinePlayer-getBedSpawnLocation.patch46
-rw-r--r--patches/server/0693-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch49
261 files changed, 20300 insertions, 21 deletions
diff --git a/patches/server/0263-Don-t-allow-digging-into-unloaded-chunks.patch b/patches/server/0263-Don-t-allow-digging-into-unloaded-chunks.patch
index 6f9db2ddeb..7603da369f 100644
--- a/patches/server/0263-Don-t-allow-digging-into-unloaded-chunks.patch
+++ b/patches/server/0263-Don-t-allow-digging-into-unloaded-chunks.patch
@@ -59,19 +59,19 @@ index 4c8189a2a7edea824545a24dccb376b8eceac001..4623c8acd125dff4919c4e2045b84831
this.level.destroyBlockProgress(this.player.getId(), pos, -1);
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index a87ea0f22b44c2fb67fd51bc8c9b0067aacac7de..1be04d0c910ebd58f2eededcdf81c94279d521c7 100644
+index a87ea0f22b44c2fb67fd51bc8c9b0067aacac7de..6ea2a25789fe7f90dae7e3de7966c88f39e929a8 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1668,6 +1668,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
case START_DESTROY_BLOCK:
case ABORT_DESTROY_BLOCK:
case STOP_DESTROY_BLOCK:
-+ // Paper start - Don't allow digging into unloaded chunks
-+ if (this.player.level().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) {
-+ this.player.connection.ackBlockChangesUpTo(packet.getSequence());
-+ return;
-+ }
-+ // Paper end - Don't allow digging into unloaded chunks
++ // Paper start - Don't allow digging into unloaded chunks
++ if (this.player.level().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) {
++ this.player.connection.ackBlockChangesUpTo(packet.getSequence());
++ return;
++ }
++ // Paper end - Don't allow digging into unloaded chunks
this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxY(), packet.getSequence());
this.player.connection.ackBlockChangesUpTo(packet.getSequence());
return;
diff --git a/patches/server/0267-Book-size-limits.patch b/patches/server/0267-Book-size-limits.patch
index e43d682a1b..2c4374aa7a 100644
--- a/patches/server/0267-Book-size-limits.patch
+++ b/patches/server/0267-Book-size-limits.patch
@@ -6,7 +6,7 @@ Subject: [PATCH] Book size limits
Puts some limits on the size of books.
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 1be04d0c910ebd58f2eededcdf81c94279d521c7..ca0aa2acbc1316201d7a9c69e4aad8ffe4d61d83 100644
+index 6ea2a25789fe7f90dae7e3de7966c88f39e929a8..fb01e24ea6fd1d1f4beabb862c70332f92a80d78 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1117,6 +1117,44 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
diff --git a/patches/server/0274-Brigadier-Mojang-API.patch b/patches/server/0274-Brigadier-Mojang-API.patch
index dcabb54ddb..9dc98d9edd 100644
--- a/patches/server/0274-Brigadier-Mojang-API.patch
+++ b/patches/server/0274-Brigadier-Mojang-API.patch
@@ -119,7 +119,7 @@ index 6212d94503023f7bb5ca21785cbb69babe4421c3..642d5c6849debc5a266605b0df30d552
if (commandnode2.canUse(source)) {
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index ca0aa2acbc1316201d7a9c69e4aad8ffe4d61d83..c32dd209e751f8c0e8e73c91a9d4b2c21c59b883 100644
+index fb01e24ea6fd1d1f4beabb862c70332f92a80d78..ba10d147705d7d775999d94053cf47b5f481b905 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -778,19 +778,34 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
diff --git a/patches/server/0275-Limit-Client-Sign-length-more.patch b/patches/server/0275-Limit-Client-Sign-length-more.patch
index 8e4daa3d06..808ca0937b 100644
--- a/patches/server/0275-Limit-Client-Sign-length-more.patch
+++ b/patches/server/0275-Limit-Client-Sign-length-more.patch
@@ -22,7 +22,7 @@ it only impacts data sent from the client.
Set -DPaper.maxSignLength=XX to change limit or -1 to disable
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index c32dd209e751f8c0e8e73c91a9d4b2c21c59b883..969fee86d2422cbadfbce18d8bbaf72ff30b5c3d 100644
+index ba10d147705d7d775999d94053cf47b5f481b905..9f984834f3da2582f21172f2d52783ad3ff5c46d 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -310,6 +310,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
diff --git a/patches/server/0347-Prevent-teleporting-dead-entities.patch b/patches/server/0347-Prevent-teleporting-dead-entities.patch
index 7582f1a3d6..7f2a2917af 100644
--- a/patches/server/0347-Prevent-teleporting-dead-entities.patch
+++ b/patches/server/0347-Prevent-teleporting-dead-entities.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Prevent teleporting dead entities
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 969fee86d2422cbadfbce18d8bbaf72ff30b5c3d..d754eda8e8111886e610bc68e8980b54b43eadb1 100644
+index 9f984834f3da2582f21172f2d52783ad3ff5c46d..e9e0a3d0958b309ca17322b11a6612be59ca41a6 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1625,6 +1625,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
diff --git a/patches/server/0362-Prevent-position-desync-causing-tp-exploit.patch b/patches/server/0362-Prevent-position-desync-causing-tp-exploit.patch
index a3f9d5b3c7..4421a00963 100644
--- a/patches/server/0362-Prevent-position-desync-causing-tp-exploit.patch
+++ b/patches/server/0362-Prevent-position-desync-causing-tp-exploit.patch
@@ -13,7 +13,7 @@ behaviour, we need to move all of this dangerous logic outside
of the move call and into an appropriate place in the tick method.
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index d754eda8e8111886e610bc68e8980b54b43eadb1..7d7fff91beafd5cf67296efaa06308f1363585a0 100644
+index e9e0a3d0958b309ca17322b11a6612be59ca41a6..b7e3c15055681b916482503f6b2370b362863c9c 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -1413,6 +1413,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
diff --git a/patches/server/0364-Add-PlayerRecipeBookClickEvent.patch b/patches/server/0364-Add-PlayerRecipeBookClickEvent.patch
index abaa3fa71e..1bda25313e 100644
--- a/patches/server/0364-Add-PlayerRecipeBookClickEvent.patch
+++ b/patches/server/0364-Add-PlayerRecipeBookClickEvent.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Add PlayerRecipeBookClickEvent
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 7d7fff91beafd5cf67296efaa06308f1363585a0..a7d1b4d4668b90445e1cba786be74fbd069d3330 100644
+index b7e3c15055681b916482503f6b2370b362863c9c..71785d4b2204030dc718c10494840013d5ea72fa 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -201,6 +201,7 @@ import net.minecraft.world.phys.Vec3;
diff --git a/patches/server/0366-Add-permission-for-command-blocks.patch b/patches/server/0366-Add-permission-for-command-blocks.patch
index 604ac9cb23..88dc3a8c71 100644
--- a/patches/server/0366-Add-permission-for-command-blocks.patch
+++ b/patches/server/0366-Add-permission-for-command-blocks.patch
@@ -18,7 +18,7 @@ index 4623c8acd125dff4919c4e2045b848310d785da5..86e4559da2344f228ef4d1c4ac3c115f
return false;
} else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index a7d1b4d4668b90445e1cba786be74fbd069d3330..1aec0f76636b1bdc5bb0b300690099ad341873e8 100644
+index 71785d4b2204030dc718c10494840013d5ea72fa..51e718812f2696f05824c735962aa885069d68e3 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -816,7 +816,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
diff --git a/patches/server/0368-Fix-Per-World-Difficulty-Remembering-Difficulty.patch b/patches/server/0368-Fix-Per-World-Difficulty-Remembering-Difficulty.patch
index cf9db52420..329a3d31a9 100644
--- a/patches/server/0368-Fix-Per-World-Difficulty-Remembering-Difficulty.patch
+++ b/patches/server/0368-Fix-Per-World-Difficulty-Remembering-Difficulty.patch
@@ -76,7 +76,7 @@ index fe1975675189c6d1a63c42b7959fa40b5ac95ef8..9a3e73a5c206b78dfcf6f41a47b61434
@Override
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 1aec0f76636b1bdc5bb0b300690099ad341873e8..11f9853bd48cec693c99edb3d43f0356a0852d5e 100644
+index 51e718812f2696f05824c735962aa885069d68e3..20d7b673df5e2def564c9b0daee1e943d7522deb 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -3366,7 +3366,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
diff --git a/patches/server/0373-Do-not-accept-invalid-client-settings.patch b/patches/server/0373-Do-not-accept-invalid-client-settings.patch
index c59997797f..5a6f7f0a1f 100644
--- a/patches/server/0373-Do-not-accept-invalid-client-settings.patch
+++ b/patches/server/0373-Do-not-accept-invalid-client-settings.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Do not accept invalid client settings
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 11f9853bd48cec693c99edb3d43f0356a0852d5e..417930ad0ffe395e9211a6bf1e6c192af659471b 100644
+index 20d7b673df5e2def564c9b0daee1e943d7522deb..d30febf2f37f2b9c864184f272d5949cbe2dc1e8 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -3352,6 +3352,13 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
diff --git a/patches/server/0401-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch b/patches/server/0401-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch
index fa6d3ee774..37e4591ad7 100644
--- a/patches/server/0401-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch
+++ b/patches/server/0401-Fix-Entity-Teleportation-and-cancel-velocity-if-tele.patch
@@ -9,7 +9,7 @@ as this is how Vanilla teleports entities.
Cancel any pending motion when teleported.
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 417930ad0ffe395e9211a6bf1e6c192af659471b..6f96f1522ae552940cf279194bd84630c3e4853c 100644
+index d30febf2f37f2b9c864184f272d5949cbe2dc1e8..748458bf569e7b96fd7cea6bd8a29d1735eb7d25 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -680,7 +680,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
diff --git a/patches/server/0411-Fix-for-large-move-vectors-crashing-server.patch b/patches/server/0411-Fix-for-large-move-vectors-crashing-server.patch
index c2cff41458..12c0f652ae 100644
--- a/patches/server/0411-Fix-for-large-move-vectors-crashing-server.patch
+++ b/patches/server/0411-Fix-for-large-move-vectors-crashing-server.patch
@@ -6,7 +6,7 @@ Subject: [PATCH] Fix for large move vectors crashing server
Check movement distance also based on current position.
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 6f96f1522ae552940cf279194bd84630c3e4853c..bfcf0a27ab8d90742b83e9c200f2ec867e2424fe 100644
+index 748458bf569e7b96fd7cea6bd8a29d1735eb7d25..8be41f7ea383a27331109813e04d807f38bf6736 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -499,9 +499,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
@@ -33,7 +33,7 @@ index 6f96f1522ae552940cf279194bd84630c3e4853c..bfcf0a27ab8d90742b83e9c200f2ec86
+ double currDeltaZ = toZ - fromZ;
+ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1);
+ double otherFieldX = d3 - this.vehicleLastGoodX;
-+ double otherFieldY = d4 - this.vehicleLastGoodY - 1.0E-6D;
++ double otherFieldY = d4 - this.vehicleLastGoodY;
+ double otherFieldZ = d5 - this.vehicleLastGoodZ;
+ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1);
+ // Paper end - fix large move vectors killing the server
@@ -48,7 +48,7 @@ index 6f96f1522ae552940cf279194bd84630c3e4853c..bfcf0a27ab8d90742b83e9c200f2ec86
- d7 = d4 - this.vehicleLastGoodY;
- d8 = d5 - this.vehicleLastGoodZ;
+ d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above
-+ d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Paper - diff on change, used for checking large move vectors above
++ d7 = d4 - this.vehicleLastGoodY; // Paper - diff on change, used for checking large move vectors above
+ d8 = d5 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above
boolean flag1 = entity.verticalCollisionBelow;
diff --git a/patches/server/0435-Limit-recipe-packets.patch b/patches/server/0435-Limit-recipe-packets.patch
index 66c9b8f393..f12a292853 100644
--- a/patches/server/0435-Limit-recipe-packets.patch
+++ b/patches/server/0435-Limit-recipe-packets.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Limit recipe packets
diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index bfcf0a27ab8d90742b83e9c200f2ec867e2424fe..65bc724ba824e4b65005f0886de768506a9b4160 100644
+index 8be41f7ea383a27331109813e04d807f38bf6736..bf4b64f07f21d816eb866d23b290c2f5e5471c70 100644
--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
@@ -279,6 +279,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
diff --git a/patches/server/0446-Allow-disabling-mob-spawner-spawn-egg-transformation.patch b/patches/server/0446-Allow-disabling-mob-spawner-spawn-egg-transformation.patch
new file mode 100644
index 0000000000..140d605ba0
--- /dev/null
+++ b/patches/server/0446-Allow-disabling-mob-spawner-spawn-egg-transformation.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BrodyBeckwith <[email protected]>
+Date: Fri, 9 Oct 2020 20:30:12 -0400
+Subject: [PATCH] Allow disabling mob spawner spawn egg transformation
+
+
+diff --git a/src/main/java/net/minecraft/world/item/SpawnEggItem.java b/src/main/java/net/minecraft/world/item/SpawnEggItem.java
+index ecc3193b4276a083461780eddab9f7b1c34175a8..cc7e9b87e919b4ef8cf77cd780c890fd9a9cfa50 100644
+--- a/src/main/java/net/minecraft/world/item/SpawnEggItem.java
++++ b/src/main/java/net/minecraft/world/item/SpawnEggItem.java
+@@ -63,6 +63,8 @@ public class SpawnEggItem extends Item {
+ EntityType entitytypes;
+
+ if (tileentity instanceof Spawner) {
++ if (world.paperConfig().entities.spawning.disableMobSpawnerSpawnEggTransformation) return InteractionResult.FAIL; // Paper - Allow disabling mob spawner spawn egg transformation
++
+ Spawner spawner = (Spawner) tileentity;
+
+ entitytypes = this.getType(world.registryAccess(), itemstack);
diff --git a/patches/server/0447-Fix-Not-a-string-Map-Conversion-spam.patch b/patches/server/0447-Fix-Not-a-string-Map-Conversion-spam.patch
new file mode 100644
index 0000000000..8952de7633
--- /dev/null
+++ b/patches/server/0447-Fix-Not-a-string-Map-Conversion-spam.patch
@@ -0,0 +1,45 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <[email protected]>
+Date: Thu, 8 Oct 2020 00:00:25 -0400
+Subject: [PATCH] Fix "Not a string" Map Conversion spam
+
+The maps did convert successfully, but had noisy logs due to Spigot
+implementing this logic incorrectly.
+
+This stops the spam by converting the old format to new before
+requesting the world.
+
+Track spigot issue to see when fixed: https://hub.spigotmc.org/jira/browse/SPIGOT-6181
+
+diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+index c21ae4975206398e7d20b37a749b830b9219c746..a89f0b652c515efbc24ffc9af47fa65082946e54 100644
+--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+@@ -123,7 +123,26 @@ public class MapItemSavedData extends SavedData {
+ }
+
+ public static MapItemSavedData load(CompoundTag nbt, HolderLookup.Provider registries) {
+- DataResult<ResourceKey<Level>> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, nbt.get("dimension"))); // CraftBukkit - decompile error
++ // Paper start - fix "Not a string" spam
++ Tag dimension = nbt.get("dimension");
++ if (dimension instanceof final net.minecraft.nbt.NumericTag numericTag && numericTag.getAsInt() >= CraftWorld.CUSTOM_DIMENSION_OFFSET) {
++ long least = nbt.getLong("UUIDLeast");
++ long most = nbt.getLong("UUIDMost");
++
++ if (least != 0L && most != 0L) {
++ UUID uuid = new UUID(most, least);
++ CraftWorld world = (CraftWorld) Bukkit.getWorld(uuid);
++ if (world != null) {
++ dimension = net.minecraft.nbt.StringTag.valueOf("minecraft:" + world.getName().toLowerCase(java.util.Locale.ENGLISH));
++ } else {
++ dimension = net.minecraft.nbt.StringTag.valueOf("bukkit:_invalidworld_");
++ }
++ } else {
++ dimension = net.minecraft.nbt.StringTag.valueOf("bukkit:_invalidworld_");
++ }
++ }
++ DataResult<ResourceKey<Level>> dataresult = DimensionType.parseLegacy(new Dynamic(NbtOps.INSTANCE, dimension)); // CraftBukkit - decompile error
++ // Paper end - fix "Not a string" spam
+ Logger logger = MapItemSavedData.LOGGER;
+
+ Objects.requireNonNull(logger);
diff --git a/patches/server/0448-Add-PlayerFlowerPotManipulateEvent.patch b/patches/server/0448-Add-PlayerFlowerPotManipulateEvent.patch
new file mode 100644
index 0000000000..8b8fb61ee2
--- /dev/null
+++ b/patches/server/0448-Add-PlayerFlowerPotManipulateEvent.patch
@@ -0,0 +1,48 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: MisterVector <[email protected]>
+Date: Tue, 13 Aug 2019 19:45:06 -0700
+Subject: [PATCH] Add PlayerFlowerPotManipulateEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java
+index 4c7723b9f369391ab253c4a60510e318fb7cfce3..f45a8741e6707f033b0205eee03c59aa889565e1 100644
+--- a/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/FlowerPotBlock.java
+@@ -63,6 +63,18 @@ public class FlowerPotBlock extends Block {
+ } else if (!this.isEmpty()) {
+ return InteractionResult.CONSUME;
+ } else {
++ // Paper start - Add PlayerFlowerPotManipulateEvent
++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
++ org.bukkit.inventory.ItemStack placedStack = org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(stack);
++
++ io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent((org.bukkit.entity.Player) player.getBukkitEntity(), block, placedStack, true);
++ if (!event.callEvent()) {
++ // Update client
++ player.containerMenu.sendAllDataToRemote();
++
++ return InteractionResult.CONSUME;
++ }
++ // Paper end - Add PlayerFlowerPotManipulateEvent
+ world.setBlock(pos, blockState, 3);
+ world.gameEvent(player, GameEvent.BLOCK_CHANGE, pos);
+ player.awardStat(Stats.POT_FLOWER);
+@@ -77,6 +89,18 @@ public class FlowerPotBlock extends Block {
+ return InteractionResult.CONSUME;
+ } else {
+ ItemStack itemStack = new ItemStack(this.potted);
++ // Paper start - Add PlayerFlowerPotManipulateEvent
++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
++ org.bukkit.inventory.ItemStack pottedStack = new org.bukkit.inventory.ItemStack(org.bukkit.craftbukkit.block.CraftBlockType.minecraftToBukkit(this.potted));
++
++ io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent event = new io.papermc.paper.event.player.PlayerFlowerPotManipulateEvent((org.bukkit.entity.Player) player.getBukkitEntity(), block, pottedStack, false);
++ if (!event.callEvent()) {
++ // Update client
++ player.containerMenu.sendAllDataToRemote();
++
++ return InteractionResult.PASS;
++ }
++ // Paper end - Add PlayerFlowerPotManipulateEvent
+ if (!player.addItem(itemStack)) {
+ player.drop(itemStack, false);
+ }
diff --git a/patches/server/0449-Fix-interact-event-not-being-called-sometimes.patch b/patches/server/0449-Fix-interact-event-not-being-called-sometimes.patch
new file mode 100644
index 0000000000..3c9078dd88
--- /dev/null
+++ b/patches/server/0449-Fix-interact-event-not-being-called-sometimes.patch
@@ -0,0 +1,48 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: TheMolkaPL <[email protected]>
+Date: Sun, 21 Jun 2020 17:21:46 +0200
+Subject: [PATCH] Fix interact event not being called sometimes
+
+* Call PlayerInteractEvent when left-clicking on a block in adventure
+ mode.
+* Call PlayerInteractEvent when left-clicking an Entity that is out of
+ range in adventure/survival (entity reach is 3.0).
+
+Co-authored-by: Moulberry <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index bf4b64f07f21d816eb866d23b290c2f5e5471c70..3d4ab22077eb358168b2959c3d1beb10b7104ab7 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -1842,7 +1842,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ } else if (enuminteractionresult instanceof InteractionResult.Success) {
+ InteractionResult.Success enuminteractionresult_d = (InteractionResult.Success) enuminteractionresult;
+
+- if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER) {
++ if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER && !this.player.gameMode.interactResult) {
+ this.player.swing(enumhand, true);
+ }
+ }
+@@ -2466,13 +2466,20 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ double d3 = Math.max(this.player.blockInteractionRange(), this.player.entityInteractionRange());
+ // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time
+ // SPIGOT-7429: Make sure to call PlayerInteractEvent for spectators and non-pickable entities
+- org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.1, entity -> {
++ org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.0, entity -> { // Paper - Call interact event; change raySize from 0.1 to 0.0
+ Entity handle = ((CraftEntity) entity).getHandle();
+ return entity != this.player.getBukkitEntity() && this.player.getBukkitEntity().canSee(entity) && !handle.isSpectator() && handle.isPickable() && !handle.isPassengerOfSameVehicle(this.player);
+ });
+ if (result == null) {
+ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND);
+- }
++ } else { // Paper start - Call interact event
++ GameType gameType = this.player.gameMode.getGameModeForPlayer();
++ if (gameType == GameType.ADVENTURE && result.getHitBlock() != null) {
++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, ((org.bukkit.craftbukkit.block.CraftBlock) result.getHitBlock()).getPosition(), org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(result.getHitBlockFace()), this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND);
++ } else if (gameType != GameType.CREATIVE && result.getHitEntity() != null && origin.toVector().distanceSquared(result.getHitPosition()) > this.player.entityInteractionRange() * this.player.entityInteractionRange()) {
++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), InteractionHand.MAIN_HAND);
++ }
++ } // Paper end - Call interact event
+
+ // Arm swing animation
+ PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING);
diff --git a/patches/server/0450-Zombie-API-breaking-doors.patch b/patches/server/0450-Zombie-API-breaking-doors.patch
new file mode 100644
index 0000000000..b0741f110f
--- /dev/null
+++ b/patches/server/0450-Zombie-API-breaking-doors.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 18 Nov 2020 11:32:46 -0800
+Subject: [PATCH] Zombie API - breaking doors
+
+== AT ==
+public net.minecraft.world.entity.monster.Zombie supportsBreakDoorGoal()Z
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java
+index 4412c913123f7521f449c98b60378e8d3b1671ce..dfc2b40e20069705f92d86a6898e3e8348bf4dcd 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftZombie.java
+@@ -122,6 +122,11 @@ public class CraftZombie extends CraftMonster implements Zombie {
+ public void setShouldBurnInDay(boolean shouldBurnInDay) {
+ getHandle().setShouldBurnInDay(shouldBurnInDay);
+ }
++
++ @Override
++ public boolean supportsBreakingDoors() {
++ return true; // All zombies are now capable of breaking doors, see https://bugs.mojang.com/browse/MC-137053
++ }
+ // Paper end
+
+ @Override
diff --git a/patches/server/0451-Fix-nerfed-slime-when-splitting.patch b/patches/server/0451-Fix-nerfed-slime-when-splitting.patch
new file mode 100644
index 0000000000..c35358b895
--- /dev/null
+++ b/patches/server/0451-Fix-nerfed-slime-when-splitting.patch
@@ -0,0 +1,18 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Mon, 24 Aug 2020 08:39:06 -0700
+Subject: [PATCH] Fix nerfed slime when splitting
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java
+index 26f4db572dc6c25a9815b8f352d8829e252fa1a2..129f0cbc0469cb2804db6088b53347d88d91f4eb 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java
+@@ -250,6 +250,7 @@ public class Slime extends Mob implements Enemy {
+ float f3 = ((float) (l / 2) - 0.5F) * f1;
+
+ Slime converted = this.convertTo(this.getType(), new ConversionParams(ConversionType.SPLIT_ON_DEATH, false, false, scoreboardteam), EntitySpawnReason.TRIGGERED, (entityslime) -> { // CraftBukkit
++ entityslime.aware = this.aware; // Paper - Fix nerfed slime when splitting
+ entityslime.setSize(j, true);
+ entityslime.moveTo(this.getX() + (double) f2, this.getY() + 0.5D, this.getZ() + (double) f3, this.random.nextFloat() * 360.0F, 0.0F);
+ // CraftBukkit start
diff --git a/patches/server/0452-Add-EntityLoadCrossbowEvent.patch b/patches/server/0452-Add-EntityLoadCrossbowEvent.patch
new file mode 100644
index 0000000000..be830ab067
--- /dev/null
+++ b/patches/server/0452-Add-EntityLoadCrossbowEvent.patch
@@ -0,0 +1,68 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: JRoy <[email protected]>
+Date: Wed, 7 Oct 2020 12:04:01 -0400
+Subject: [PATCH] Add EntityLoadCrossbowEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/item/CrossbowItem.java b/src/main/java/net/minecraft/world/item/CrossbowItem.java
+index a81e2f1e5abc20a95c562c1b9b1f7af489eaaaab..be1902a307a54434644b242b429ad47c271d2a0c 100644
+--- a/src/main/java/net/minecraft/world/item/CrossbowItem.java
++++ b/src/main/java/net/minecraft/world/item/CrossbowItem.java
+@@ -90,7 +90,14 @@ public class CrossbowItem extends ProjectileWeaponItem {
+ public boolean releaseUsing(ItemStack stack, Level world, LivingEntity user, int remainingUseTicks) {
+ int i = this.getUseDuration(stack, user) - remainingUseTicks;
+ float f = getPowerForTime(i, stack, user);
+- if (f >= 1.0F && !isCharged(stack) && tryLoadProjectiles(user, stack)) {
++ // Paper start - Add EntityLoadCrossbowEvent
++ if (f >= 1.0F && !isCharged(stack)) {
++ final io.papermc.paper.event.entity.EntityLoadCrossbowEvent event = new io.papermc.paper.event.entity.EntityLoadCrossbowEvent(user.getBukkitLivingEntity(), stack.asBukkitMirror(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(user.getUsedItemHand()));
++ if (!event.callEvent() || !tryLoadProjectiles(user, stack, event.shouldConsumeItem()) || !event.shouldConsumeItem()) {
++ if (user instanceof ServerPlayer player) player.containerMenu.sendAllDataToRemote();
++ return false;
++ }
++ // Paper end - Add EntityLoadCrossbowEvent
+ CrossbowItem.ChargingSounds chargingSounds = this.getChargingSounds(stack);
+ chargingSounds.end()
+ .ifPresent(
+@@ -111,8 +118,14 @@ public class CrossbowItem extends ProjectileWeaponItem {
+ }
+ }
+
+- private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow) {
+- List<ItemStack> list = draw(crossbow, shooter.getProjectile(crossbow), shooter);
++ @io.papermc.paper.annotation.DoNotUse // Paper - Add EntityLoadCrossbowEvent
++ private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow) {
++ // Paper start - Add EntityLoadCrossbowEvent
++ return CrossbowItem.tryLoadProjectiles(shooter, crossbow, true);
++ }
++ private static boolean tryLoadProjectiles(LivingEntity shooter, ItemStack crossbow, boolean consume) {
++ List<ItemStack> list = draw(crossbow, shooter.getProjectile(crossbow), shooter, consume);
++ // Paper end - Add EntityLoadCrossbowEvent
+ if (!list.isEmpty()) {
+ crossbow.set(DataComponents.CHARGED_PROJECTILES, ChargedProjectiles.of(list));
+ return true;
+diff --git a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java
+index a7d0ac6513fd888e222b3128afc1a227ea91f1d2..78ba170a83f8c026bd110eae494c52577182ed61 100644
+--- a/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java
++++ b/src/main/java/net/minecraft/world/item/ProjectileWeaponItem.java
+@@ -109,6 +109,11 @@ public abstract class ProjectileWeaponItem extends Item {
+ }
+
+ protected static List<ItemStack> draw(ItemStack stack, ItemStack projectileStack, LivingEntity shooter) {
++ // Paper start
++ return draw(stack, projectileStack, shooter, true);
++ }
++ protected static List<ItemStack> draw(ItemStack stack, ItemStack projectileStack, LivingEntity shooter, boolean consume) {
++ // Paper end
+ if (projectileStack.isEmpty()) {
+ return List.of();
+ } else {
+@@ -128,7 +133,7 @@ public abstract class ProjectileWeaponItem extends Item {
+ ItemStack itemstack2 = projectileStack.copy();
+
+ for (int k = 0; k < j; ++k) {
+- ItemStack itemstack3 = ProjectileWeaponItem.useAmmo(stack, k == 0 ? projectileStack : itemstack2, shooter, k > 0);
++ ItemStack itemstack3 = ProjectileWeaponItem.useAmmo(stack, k == 0 ? projectileStack : itemstack2, shooter, k > 0 || !consume); // Paper
+
+ if (!itemstack3.isEmpty()) {
+ list.add(itemstack3);
diff --git a/patches/server/0453-Add-WorldGameRuleChangeEvent.patch b/patches/server/0453-Add-WorldGameRuleChangeEvent.patch
new file mode 100644
index 0000000000..a35834efa7
--- /dev/null
+++ b/patches/server/0453-Add-WorldGameRuleChangeEvent.patch
@@ -0,0 +1,100 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 20 Dec 2020 16:41:44 -0800
+Subject: [PATCH] Add WorldGameRuleChangeEvent
+
+
+diff --git a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java
+index 6fc70f07f9ba964ff6f5176367dab788decfc917..da0600ab3b0a983ac36cb777d21160bdaced7c52 100644
+--- a/src/main/java/net/minecraft/server/commands/GameRuleCommand.java
++++ b/src/main/java/net/minecraft/server/commands/GameRuleCommand.java
+@@ -36,7 +36,7 @@ public class GameRuleCommand {
+ CommandSourceStack commandlistenerwrapper = (CommandSourceStack) context.getSource();
+ T t0 = commandlistenerwrapper.getLevel().getGameRules().getRule(key); // CraftBukkit
+
+- t0.setFromArgument(context, "value");
++ t0.setFromArgument(context, "value", key); // Paper - Add WorldGameRuleChangeEvent
+ commandlistenerwrapper.sendSuccess(() -> {
+ return Component.translatable("commands.gamerule.set", key.getId(), t0.toString());
+ }, true);
+diff --git a/src/main/java/net/minecraft/world/level/GameRules.java b/src/main/java/net/minecraft/world/level/GameRules.java
+index 7ea92a0b0f5d4eb6bd873e61c42bc0499d5d2028..09299e45552eb998fd02123c3921c0653f85083d 100644
+--- a/src/main/java/net/minecraft/world/level/GameRules.java
++++ b/src/main/java/net/minecraft/world/level/GameRules.java
+@@ -322,10 +322,10 @@ public class GameRules {
+ this.type = type;
+ }
+
+- protected abstract void updateFromArgument(CommandContext<CommandSourceStack> context, String name);
++ protected abstract void updateFromArgument(CommandContext<CommandSourceStack> context, String name, GameRules.Key<T> gameRuleKey); // Paper - Add WorldGameRuleChangeEvent
+
+- public void setFromArgument(CommandContext<CommandSourceStack> context, String name) {
+- this.updateFromArgument(context, name);
++ public void setFromArgument(CommandContext<CommandSourceStack> context, String name, GameRules.Key<T> gameRuleKey) { // Paper - Add WorldGameRuleChangeEvent
++ this.updateFromArgument(context, name, gameRuleKey); // Paper - Add WorldGameRuleChangeEvent
+ this.onChanged(((CommandSourceStack) context.getSource()).getLevel()); // CraftBukkit - per-world
+ }
+
+@@ -383,8 +383,11 @@ public class GameRules {
+ }
+
+ @Override
+- protected void updateFromArgument(CommandContext<CommandSourceStack> context, String name) {
+- this.value = BoolArgumentType.getBool(context, name);
++ protected void updateFromArgument(CommandContext<CommandSourceStack> context, String name, GameRules.Key<BooleanValue> gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent
++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule<Boolean>) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(BoolArgumentType.getBool(context, name)));
++ if (!event.callEvent()) return;
++ this.value = Boolean.parseBoolean(event.getValue());
++ // Paper end - Add WorldGameRuleChangeEvent
+ }
+
+ public boolean get() {
+@@ -456,8 +459,11 @@ public class GameRules {
+ }
+
+ @Override
+- protected void updateFromArgument(CommandContext<CommandSourceStack> context, String name) {
+- this.value = IntegerArgumentType.getInteger(context, name);
++ protected void updateFromArgument(CommandContext<CommandSourceStack> context, String name, GameRules.Key<IntegerValue> gameRuleKey) { // Paper start - Add WorldGameRuleChangeEvent
++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(context.getSource().getBukkitWorld(), context.getSource().getBukkitSender(), (org.bukkit.GameRule<Integer>) org.bukkit.GameRule.getByName(gameRuleKey.toString()), String.valueOf(IntegerArgumentType.getInteger(context, name)));
++ if (!event.callEvent()) return;
++ this.value = Integer.parseInt(event.getValue());
++ // Paper end - Add WorldGameRuleChangeEvent
+ }
+
+ public int get() {
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index b877904bd18c96a4a7e49fb3e1aba2b6109f15cd..69464a4fe467128121fe1a9ba08c9c7154e22c7f 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -1884,9 +1884,14 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+ if (rule == null || value == null) return false;
+
+ if (!this.isGameRule(rule)) return false;
++ // Paper start - Add WorldGameRuleChangeEvent
++ GameRule<?> gameRule = GameRule.getByName(rule);
++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, gameRule, value);
++ if (!event.callEvent()) return false;
++ // Paper end - Add WorldGameRuleChangeEvent
+
+ GameRules.Value<?> handle = this.getHandle().getGameRules().getRule(this.getGameRulesNMS().get(rule));
+- handle.deserialize(value);
++ handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent
+ handle.onChanged(this.getHandle());
+ return true;
+ }
+@@ -1921,9 +1926,13 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+ Preconditions.checkArgument(newValue != null, "GameRule value cannot be null");
+
+ if (!this.isGameRule(rule.getName())) return false;
++ // Paper start - Add WorldGameRuleChangeEvent
++ io.papermc.paper.event.world.WorldGameRuleChangeEvent event = new io.papermc.paper.event.world.WorldGameRuleChangeEvent(this, null, rule, String.valueOf(newValue));
++ if (!event.callEvent()) return false;
++ // Paper end - Add WorldGameRuleChangeEvent
+
+ GameRules.Value<?> handle = this.getHandle().getGameRules().getRule(this.getGameRulesNMS().get(rule.getName()));
+- handle.deserialize(newValue.toString());
++ handle.deserialize(event.getValue()); // Paper - Add WorldGameRuleChangeEvent
+ handle.onChanged(this.getHandle());
+ return true;
+ }
diff --git a/patches/server/0454-Add-ServerResourcesReloadedEvent.patch b/patches/server/0454-Add-ServerResourcesReloadedEvent.patch
new file mode 100644
index 0000000000..b6ef8ff126
--- /dev/null
+++ b/patches/server/0454-Add-ServerResourcesReloadedEvent.patch
@@ -0,0 +1,54 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 2 Dec 2020 20:04:01 -0800
+Subject: [PATCH] Add ServerResourcesReloadedEvent
+
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index c036c391172ffb4b14b7a5f77776a68e3670775c..d6fe710a4e3784ba38776a0496eac7fa702aed88 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -2150,7 +2150,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ return this.functionManager;
+ }
+
++ // Paper start - Add ServerResourcesReloadedEvent
++ @Deprecated @io.papermc.paper.annotation.DoNotUse
+ public CompletableFuture<Void> reloadResources(Collection<String> dataPacks) {
++ return this.reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN);
++ }
++ public CompletableFuture<Void> reloadResources(Collection<String> dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause cause) {
++ // Paper end - Add ServerResourcesReloadedEvent
+ CompletableFuture<Void> completablefuture = CompletableFuture.supplyAsync(() -> {
+ Stream<String> stream = dataPacks.stream(); // CraftBukkit - decompile error
+ PackRepository resourcepackrepository = this.packRepository;
+@@ -2185,6 +2191,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ this.structureTemplateManager.onResourceManagerReload(this.resources.resourceManager);
+ this.fuelValues = FuelValues.vanillaBurnTimes(this.registries.compositeAccess(), this.worldData.enabledFeatures());
+ org.bukkit.craftbukkit.block.data.CraftBlockData.reloadCache(); // Paper - cache block data strings; they can be defined by datapacks so refresh it here
++ new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper - Add ServerResourcesReloadedEvent; fire after everything has been reloaded
+ }, this);
+
+ if (this.isSameThread()) {
+diff --git a/src/main/java/net/minecraft/server/commands/ReloadCommand.java b/src/main/java/net/minecraft/server/commands/ReloadCommand.java
+index fa18d018a8458b30c0048f7e59aea39f928d974a..c020c86194723a5c89816f91e0b7c5eeaf132b7e 100644
+--- a/src/main/java/net/minecraft/server/commands/ReloadCommand.java
++++ b/src/main/java/net/minecraft/server/commands/ReloadCommand.java
+@@ -20,7 +20,7 @@ public class ReloadCommand {
+ public ReloadCommand() {}
+
+ public static void reloadPacks(Collection<String> dataPacks, CommandSourceStack source) {
+- source.getServer().reloadResources(dataPacks).exceptionally((throwable) -> {
++ source.getServer().reloadResources(dataPacks, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.COMMAND).exceptionally((throwable) -> { // Paper - Add ServerResourcesReloadedEvent
+ ReloadCommand.LOGGER.warn("Failed to execute reload", throwable);
+ source.sendFailure(Component.translatable("commands.reload.failure"));
+ return null;
+@@ -50,7 +50,7 @@ public class ReloadCommand {
+ WorldData savedata = minecraftserver.getWorldData();
+ Collection<String> collection = resourcepackrepository.getSelectedIds();
+ Collection<String> collection1 = ReloadCommand.discoverNewPacks(resourcepackrepository, savedata, collection);
+- minecraftserver.reloadResources(collection1);
++ minecraftserver.reloadResources(collection1, io.papermc.paper.event.server.ServerResourcesReloadedEvent.Cause.PLUGIN); // Paper - Add ServerResourcesReloadedEvent
+ }
+ // CraftBukkit end
+
diff --git a/patches/server/0455-Add-world-settings-for-mobs-picking-up-loot.patch b/patches/server/0455-Add-world-settings-for-mobs-picking-up-loot.patch
new file mode 100644
index 0000000000..284e61124a
--- /dev/null
+++ b/patches/server/0455-Add-world-settings-for-mobs-picking-up-loot.patch
@@ -0,0 +1,32 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 28 Nov 2020 18:43:52 -0800
+Subject: [PATCH] Add world settings for mobs picking up loot
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
+index b7b567192f14ca8bac24b533dcbb5b5bd4f5a3aa..7c80c79a87ec438d0891c5c977e162f272d80039 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
+@@ -152,7 +152,7 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
+ this.populateDefaultEquipmentSlots(randomsource, difficulty);
+ this.populateDefaultEquipmentEnchantments(world, randomsource, difficulty);
+ this.reassessWeaponGoal();
+- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier());
++ this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.skeletons || randomsource.nextFloat() < 0.55F * difficulty.getSpecialMultiplier()); // Paper - Add world settings for mobs picking up loot
+ if (this.getItemBySlot(EquipmentSlot.HEAD).isEmpty()) {
+ LocalDate localdate = LocalDate.now();
+ int i = localdate.get(ChronoField.DAY_OF_MONTH);
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
+index c182bdcc5da5652f8b34b4cb8d28651cf79009fe..34e46a64b3638f749a571d080fd8e7ac1f57edba 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
+@@ -518,7 +518,7 @@ public class Zombie extends Monster {
+ float f = difficulty.getSpecialMultiplier();
+
+ if (spawnReason != EntitySpawnReason.CONVERSION) {
+- this.setCanPickUpLoot(randomsource.nextFloat() < 0.55F * f);
++ this.setCanPickUpLoot(this.level().paperConfig().entities.behavior.mobsCanAlwaysPickUpLoot.zombies || randomsource.nextFloat() < 0.55F * f); // Paper - Add world settings for mobs picking up loot
+ }
+
+ if (object == null) {
diff --git a/patches/server/0456-Add-BlockFailedDispenseEvent.patch b/patches/server/0456-Add-BlockFailedDispenseEvent.patch
new file mode 100644
index 0000000000..7d8f896e06
--- /dev/null
+++ b/patches/server/0456-Add-BlockFailedDispenseEvent.patch
@@ -0,0 +1,50 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: TheViperShow <[email protected]>
+Date: Wed, 22 Apr 2020 09:40:38 +0200
+Subject: [PATCH] Add BlockFailedDispenseEvent
+
+
+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 975c02461bb88d71a0e3efe91838fad3fd346587..d915ef1030728a3f6ff303977784097b712238d4 100644
+--- a/src/main/java/net/minecraft/world/level/block/DispenserBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/DispenserBlock.java
+@@ -98,8 +98,10 @@ public class DispenserBlock extends BaseEntityBlock {
+ int i = tileentitydispenser.getRandomSlot(world.random);
+
+ if (i < 0) {
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) { // Paper - Add BlockFailedDispenseEvent
+ world.levelEvent(1001, pos, 0);
+ world.gameEvent((Holder) GameEvent.BLOCK_ACTIVATE, pos, GameEvent.Context.of(tileentitydispenser.getBlockState()));
++ } // Paper - Add BlockFailedDispenseEvent
+ } else {
+ ItemStack itemstack = tileentitydispenser.getItem(i);
+ DispenseItemBehavior idispensebehavior = this.getDispenseMethod(world, 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 a08e8571f3a83afc80c2f1758a9029cd28ed6947..91b514967405115f22edf4255775361a672e5c2f 100644
+--- a/src/main/java/net/minecraft/world/level/block/DropperBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/DropperBlock.java
+@@ -60,6 +60,7 @@ public class DropperBlock extends DispenserBlock {
+ int i = tileentitydispenser.getRandomSlot(world.random);
+
+ if (i < 0) {
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFailedDispenseEvent(world, pos)) // Paper - Add BlockFailedDispenseEvent
+ world.levelEvent(1001, pos, 0);
+ } else {
+ ItemStack itemstack = tileentitydispenser.getItem(i);
+diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+index f8751bf07479d6619ec1acb19f84a9af00ecd308..696867479e74c3c94e4131f2bbb97c857ed5e9dd 100644
+--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+@@ -2125,4 +2125,12 @@ public class CraftEventFactory {
+ return org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getPotion());
+ }
+ // Paper end - WitchReadyPotionEvent
++
++ // Paper start
++ public static boolean handleBlockFailedDispenseEvent(ServerLevel serverLevel, BlockPos pos) {
++ org.bukkit.block.Block block = CraftBlock.at(serverLevel, pos);
++ io.papermc.paper.event.block.BlockFailedDispenseEvent event = new io.papermc.paper.event.block.BlockFailedDispenseEvent(block);
++ return event.callEvent();
++ }
++ // Paper end
+ }
diff --git a/patches/server/0457-Add-PlayerLecternPageChangeEvent.patch b/patches/server/0457-Add-PlayerLecternPageChangeEvent.patch
new file mode 100644
index 0000000000..15052d962d
--- /dev/null
+++ b/patches/server/0457-Add-PlayerLecternPageChangeEvent.patch
@@ -0,0 +1,46 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Mon, 23 Nov 2020 12:58:51 -0800
+Subject: [PATCH] Add PlayerLecternPageChangeEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/inventory/LecternMenu.java b/src/main/java/net/minecraft/world/inventory/LecternMenu.java
+index 1b3119751617366cf753008d38be566cd7ee2453..df4ae5d37b9aa5b8fb26c5773a47a5a85f831982 100644
+--- a/src/main/java/net/minecraft/world/inventory/LecternMenu.java
++++ b/src/main/java/net/minecraft/world/inventory/LecternMenu.java
+@@ -64,6 +64,7 @@ public class LecternMenu extends AbstractContainerMenu {
+ @Override
+ public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) {
+ int j;
++ io.papermc.paper.event.player.PlayerLecternPageChangeEvent playerLecternPageChangeEvent; CraftInventoryLectern bukkitView; // Paper - Add PlayerLecternPageChangeEvent
+
+ if (id >= 100) {
+ j = id - 100;
+@@ -73,11 +74,25 @@ public class LecternMenu extends AbstractContainerMenu {
+ switch (id) {
+ case 1:
+ j = this.lecternData.get(0);
+- this.setData(0, j - 1);
++ // Paper start - Add PlayerLecternPageChangeEvent
++ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory();
++ playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.LEFT, j, j - 1);
++ if (!playerLecternPageChangeEvent.callEvent()) {
++ return false;
++ }
++ this.setData(0, playerLecternPageChangeEvent.getNewPage());
++ // Paper end - Add PlayerLecternPageChangeEvent
+ return true;
+ case 2:
+ j = this.lecternData.get(0);
+- this.setData(0, j + 1);
++ // Paper start - Add PlayerLecternPageChangeEvent
++ bukkitView = (CraftInventoryLectern) getBukkitView().getTopInventory();
++ playerLecternPageChangeEvent = new io.papermc.paper.event.player.PlayerLecternPageChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), bukkitView.getHolder(), bukkitView.getBook(), io.papermc.paper.event.player.PlayerLecternPageChangeEvent.PageChangeDirection.RIGHT, j, j + 1);
++ if (!playerLecternPageChangeEvent.callEvent()) {
++ return false;
++ }
++ this.setData(0, playerLecternPageChangeEvent.getNewPage());
++ // Paper end - Add PlayerLecternPageChangeEvent
+ return true;
+ case 3:
+ if (!player.mayBuild()) {
diff --git a/patches/server/0458-Add-PlayerLoomPatternSelectEvent.patch b/patches/server/0458-Add-PlayerLoomPatternSelectEvent.patch
new file mode 100644
index 0000000000..b523b29e0f
--- /dev/null
+++ b/patches/server/0458-Add-PlayerLoomPatternSelectEvent.patch
@@ -0,0 +1,45 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 25 Nov 2020 16:33:27 -0800
+Subject: [PATCH] Add PlayerLoomPatternSelectEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/inventory/LoomMenu.java b/src/main/java/net/minecraft/world/inventory/LoomMenu.java
+index 58470d8e9a4ceb1eca05b342481ed8260588e225..1b7cf165ab0818792870f43719a6324b282bb57a 100644
+--- a/src/main/java/net/minecraft/world/inventory/LoomMenu.java
++++ b/src/main/java/net/minecraft/world/inventory/LoomMenu.java
+@@ -162,8 +162,32 @@ public class LoomMenu extends AbstractContainerMenu {
+ @Override
+ public boolean clickMenuButton(net.minecraft.world.entity.player.Player player, int id) {
+ if (id >= 0 && id < this.selectablePatterns.size()) {
+- this.selectedBannerPatternIndex.set(id);
+- this.setupResultSlot((Holder) this.selectablePatterns.get(id));
++ // Paper start - Add PlayerLoomPatternSelectEvent
++ int selectablePatternIndex = id;
++ io.papermc.paper.event.player.PlayerLoomPatternSelectEvent event = new io.papermc.paper.event.player.PlayerLoomPatternSelectEvent((Player) player.getBukkitEntity(), ((CraftInventoryLoom) getBukkitView().getTopInventory()), org.bukkit.craftbukkit.block.banner.CraftPatternType.minecraftHolderToBukkit(this.selectablePatterns.get(selectablePatternIndex)));
++ if (!event.callEvent()) {
++ player.containerMenu.sendAllDataToRemote();
++ return false;
++ }
++ final Holder<BannerPattern> eventPattern = org.bukkit.craftbukkit.block.banner.CraftPatternType.bukkitToMinecraftHolder(event.getPatternType());
++ Holder<BannerPattern> selectedPattern = null;
++ for (int i = 0; i < this.selectablePatterns.size(); i++) {
++ final Holder<BannerPattern> holder = this.selectablePatterns.get(i);
++ if (eventPattern.equals(holder)) {
++ selectablePatternIndex = i;
++ selectedPattern = holder;
++ break;
++ }
++ }
++ if (selectedPattern == null) {
++ selectedPattern = eventPattern;
++ selectablePatternIndex = -1;
++ }
++
++ player.containerMenu.sendAllDataToRemote();
++ this.selectedBannerPatternIndex.set(selectablePatternIndex);
++ this.setupResultSlot(java.util.Objects.requireNonNull(selectedPattern, "selectedPattern was null, this is unexpected"));
++ // Paper end - Add PlayerLoomPatternSelectEvent
+ return true;
+ } else {
+ return false;
diff --git a/patches/server/0459-Configurable-door-breaking-difficulty.patch b/patches/server/0459-Configurable-door-breaking-difficulty.patch
new file mode 100644
index 0000000000..94b37c735f
--- /dev/null
+++ b/patches/server/0459-Configurable-door-breaking-difficulty.patch
@@ -0,0 +1,37 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 3 Jan 2021 22:27:43 -0800
+Subject: [PATCH] Configurable door breaking difficulty
+
+== AT ==
+public net.minecraft.world.entity.monster.Vindicator DOOR_BREAKING_PREDICATE
+public net.minecraft.world.entity.monster.Zombie DOOR_BREAKING_PREDICATE
+
+Co-authored-by: Doc <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
+index b06eedb1cb13771bbc7d0b812a9df864d1f73142..96b105697c91314148fd1b783501389214b1a3f0 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Vindicator.java
+@@ -184,7 +184,7 @@ public class Vindicator extends AbstractIllager {
+
+ static class VindicatorBreakDoorGoal extends BreakDoorGoal {
+ public VindicatorBreakDoorGoal(Mob mob) {
+- super(mob, 6, Vindicator.DOOR_BREAKING_PREDICATE);
++ super(mob, 6, com.google.common.base.Predicates.in(mob.level().paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(mob.getType(), mob.level().paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.VINDICATOR)))); // Paper - Configurable door breaking difficulty
+ this.setFlags(EnumSet.of(Goal.Flag.MOVE));
+ }
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
+index 34e46a64b3638f749a571d080fd8e7ac1f57edba..a835ec6e063dd247a008da84446f8647f38d89d4 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
+@@ -103,7 +103,7 @@ public class Zombie extends Monster {
+
+ public Zombie(EntityType<? extends Zombie> type, Level world) {
+ super(type, world);
+- this.breakDoorGoal = new BreakDoorGoal(this, Zombie.DOOR_BREAKING_PREDICATE);
++ this.breakDoorGoal = new BreakDoorGoal(this, com.google.common.base.Predicates.in(world.paperConfig().entities.behavior.doorBreakingDifficulty.getOrDefault(type, world.paperConfig().entities.behavior.doorBreakingDifficulty.get(EntityType.ZOMBIE)))); // Paper - Configurable door breaking difficulty
+ }
+
+ public Zombie(Level world) {
diff --git a/patches/server/0460-Empty-commands-shall-not-be-dispatched.patch b/patches/server/0460-Empty-commands-shall-not-be-dispatched.patch
new file mode 100644
index 0000000000..9d321da35f
--- /dev/null
+++ b/patches/server/0460-Empty-commands-shall-not-be-dispatched.patch
@@ -0,0 +1,18 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Mariell Hoversholm <[email protected]>
+Date: Wed, 6 Jan 2021 23:38:43 +0100
+Subject: [PATCH] Empty commands shall not be dispatched
+
+
+diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java
+index b34abf66c7dc756e88e08637af976f7794144d06..2dcb1a1bdba1d1cad3caa117d85a8619ed9e67b4 100644
+--- a/src/main/java/net/minecraft/commands/Commands.java
++++ b/src/main/java/net/minecraft/commands/Commands.java
+@@ -291,6 +291,7 @@ public class Commands {
+ command = event.getCommand();
+
+ String[] args = command.split(" ");
++ if (args.length == 0) return; // Paper - empty commands shall not be dispatched
+
+ String cmd = args[0];
+ if (cmd.startsWith("minecraft:")) cmd = cmd.substring("minecraft:".length());
diff --git a/patches/server/0461-Remove-stale-POIs.patch b/patches/server/0461-Remove-stale-POIs.patch
new file mode 100644
index 0000000000..1f2723ec86
--- /dev/null
+++ b/patches/server/0461-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 1ed0940d8bdc4210992a335b3691c1cd3a13580a..e56f9e823f9460854c6f0f2f05422136c12aabba 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -1786,6 +1786,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/0462-Fix-villager-boat-exploit.patch b/patches/server/0462-Fix-villager-boat-exploit.patch
new file mode 100644
index 0000000000..f5548b98cc
--- /dev/null
+++ b/patches/server/0462-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 8566e28d18a5b1221ba4e5d55e7788f80f956296..fbe10666c4ec6caa95aa38a6f99ccca700e9a4d2 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -544,6 +544,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/0463-Add-sendOpLevel-API.patch b/patches/server/0463-Add-sendOpLevel-API.patch
new file mode 100644
index 0000000000..44d11dcef6
--- /dev/null
+++ b/patches/server/0463-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 fbe10666c4ec6caa95aa38a6f99ccca700e9a4d2..061bba184c8bc2569ce1d413435ec1360fa14007 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -1025,6 +1025,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;
+
+@@ -1039,8 +1044,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 0aaea6091c5828782ed606d250423f3d75b9b27f..c93ec7e97c9af44ed75e7ea4fb1c6c58c2b6bc1a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+@@ -684,6 +684,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/0464-Add-RegistryAccess-for-managing-Registries.patch b/patches/server/0464-Add-RegistryAccess-for-managing-Registries.patch
new file mode 100644
index 0000000000..9c82b85992
--- /dev/null
+++ b/patches/server/0464-Add-RegistryAccess-for-managing-Registries.patch
@@ -0,0 +1,1405 @@
+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..cd8a6a4c2a63029f8f859765088c227bbd456813
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistries.java
+@@ -0,0 +1,154 @@
++package io.papermc.paper.registry;
++
++import com.google.common.base.Preconditions;
++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.Art;
++import org.bukkit.Fluid;
++import org.bukkit.GameEvent;
++import org.bukkit.JukeboxSong;
++import org.bukkit.Keyed;
++import org.bukkit.MusicInstrument;
++import org.bukkit.Sound;
++import org.bukkit.attribute.Attribute;
++import org.bukkit.block.Biome;
++import org.bukkit.block.BlockType;
++import org.bukkit.block.banner.PatternType;
++import org.bukkit.craftbukkit.CraftArt;
++import org.bukkit.craftbukkit.CraftFluid;
++import org.bukkit.craftbukkit.CraftGameEvent;
++import org.bukkit.craftbukkit.CraftJukeboxSong;
++import org.bukkit.craftbukkit.CraftMusicInstrument;
++import org.bukkit.craftbukkit.CraftSound;
++import org.bukkit.craftbukkit.attribute.CraftAttribute;
++import org.bukkit.craftbukkit.block.CraftBiome;
++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.jspecify.annotations.Nullable;
++
++import static io.papermc.paper.registry.entry.RegistryEntry.apiOnly;
++import static io.papermc.paper.registry.entry.RegistryEntry.entry;
++
++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.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),
++ entry(Registries.ATTRIBUTE, RegistryKey.ATTRIBUTE, Attribute.class, CraftAttribute::new),
++ entry(Registries.FLUID, RegistryKey.FLUID, Fluid.class, CraftFluid::new),
++ entry(Registries.SOUND_EVENT, RegistryKey.SOUND_EVENT, Sound.class, CraftSound::new),
++
++ // data-drivens
++ entry(Registries.BIOME, RegistryKey.BIOME, Biome.class, CraftBiome::new).delayed(),
++ 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(),
++ entry(Registries.PAINTING_VARIANT, RegistryKey.PAINTING_VARIANT, Art.class, CraftArt::new).delayed(),
++ entry(Registries.INSTRUMENT, RegistryKey.INSTRUMENT, MusicInstrument.class, CraftMusicInstrument::new).delayed(),
++
++ // api-only
++ apiOnly(Registries.ENTITY_TYPE, RegistryKey.ENTITY_TYPE, PaperSimpleRegistry::entityType),
++ apiOnly(Registries.PARTICLE_TYPE, RegistryKey.PARTICLE_TYPE, PaperSimpleRegistry::particleType),
++ apiOnly(Registries.POTION, RegistryKey.POTION, PaperSimpleRegistry::potion),
++ apiOnly(Registries.MEMORY_MODULE_TYPE, RegistryKey.MEMORY_MODULE_TYPE, () -> (org.bukkit.Registry<MemoryKey<?>>) (org.bukkit.Registry) org.bukkit.Registry.MEMORY_MODULE_TYPE)
++ );
++ final Map<RegistryKey<?>, RegistryEntry<?, ?>> byRegistryKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size());
++ final Map<ResourceKey<?>, RegistryEntry<?, ?>> byResourceKey = new IdentityHashMap<>(REGISTRY_ENTRIES.size());
++ for (final RegistryEntry<?, ?> entry : REGISTRY_ENTRIES) {
++ Preconditions.checkState(byRegistryKey.put(entry.apiKey(), entry) == null, "Duplicate api registry key: %s", entry.apiKey());
++ Preconditions.checkState(byResourceKey.put(entry.mcKey(), entry) == null, "Duplicate mc registry key: %s", entry.mcKey());
++ }
++ 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..4bf7915867dbe762ef0b070d67d5f7b7d1ee4f03
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/PaperRegistryAccess.java
+@@ -0,0 +1,122 @@
++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.jetbrains.annotations.VisibleForTesting;
++import org.jspecify.annotations.Nullable;
++
++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 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 RegistryEntry<?, T> entry = PaperRegistries.getEntry(registryKey);
++ final 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/PaperSimpleRegistry.java b/src/main/java/io/papermc/paper/registry/PaperSimpleRegistry.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..6d134ace042758da722960cbcb48e52508dafd61
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/PaperSimpleRegistry.java
+@@ -0,0 +1,38 @@
++package io.papermc.paper.registry;
++
++import java.util.function.Predicate;
++import net.minecraft.core.registries.BuiltInRegistries;
++import org.bukkit.Keyed;
++import org.bukkit.Particle;
++import org.bukkit.Registry;
++import org.bukkit.entity.EntityType;
++import org.bukkit.potion.PotionType;
++import org.jspecify.annotations.NullMarked;
++
++@NullMarked
++public class PaperSimpleRegistry<T extends Enum<T> & Keyed, M> extends Registry.SimpleRegistry<T> {
++
++ static Registry<EntityType> entityType() {
++ return new PaperSimpleRegistry<>(EntityType.class, entity -> entity != EntityType.UNKNOWN, BuiltInRegistries.ENTITY_TYPE);
++ }
++
++ static Registry<Particle> particleType() {
++ return new PaperSimpleRegistry<>(Particle.class, BuiltInRegistries.PARTICLE_TYPE);
++ }
++
++ static Registry<PotionType> potion() {
++ return new PaperSimpleRegistry<>(PotionType.class, BuiltInRegistries.POTION);
++ }
++
++ private final net.minecraft.core.Registry<M> nmsRegistry;
++
++ protected PaperSimpleRegistry(final Class<T> type, final net.minecraft.core.Registry<M> nmsRegistry) {
++ super(type);
++ this.nmsRegistry = nmsRegistry;
++ }
++
++ public PaperSimpleRegistry(final Class<T> type, final Predicate<T> predicate, final net.minecraft.core.Registry<M> nmsRegistry) {
++ super(type, predicate);
++ this.nmsRegistry = nmsRegistry;
++ }
++}
+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..1b52d4bc868e1e5f84c8416301e193bb9cd315b2
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/RegistryHolder.java
+@@ -0,0 +1,44 @@
++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;
++
++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..45cbc425da64f0bd3290600869ad425d9e6e912b
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/CraftRegistryEntry.java
+@@ -0,0 +1,48 @@
++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;
++
++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..2889d87f0989ae5744cd4c1e57240830aa574155
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/RegistryEntry.java
+@@ -0,0 +1,48 @@
++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;
++
++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..f04e93aa5ca41ce1cf3b152f827911fdf0dd10cb
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/entry/package-info.java
+@@ -0,0 +1,4 @@
++@NullMarked
++package io.papermc.paper.registry.entry;
++
++import org.jspecify.annotations.NullMarked;
+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..ca829b162d4369f845e59b62bb8779fd83fe2ef3
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/legacy/DelayedRegistry.java
+@@ -0,0 +1,57 @@
++package io.papermc.paper.registry.legacy;
++
++import java.util.Iterator;
++import java.util.function.Supplier;
++import java.util.stream.Stream;
++import org.bukkit.Keyed;
++import org.bukkit.NamespacedKey;
++import org.bukkit.Registry;
++import org.jspecify.annotations.Nullable;
++
++/**
++ * 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 @Nullable 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 T getOrThrow(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 @Nullable 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..063a8d7a7313d5d685d51df54d9502d72266da49
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/legacy/package-info.java
+@@ -0,0 +1,4 @@
++@NullMarked
++package io.papermc.paper.registry.legacy;
++
++import org.jspecify.annotations.NullMarked;
+diff --git a/src/main/java/io/papermc/paper/registry/package-info.java b/src/main/java/io/papermc/paper/registry/package-info.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..ca07ef31161e938d48214992b34cafb712a51513
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/registry/package-info.java
+@@ -0,0 +1,4 @@
++@NullMarked
++package io.papermc.paper.registry;
++
++import org.jspecify.annotations.NullMarked;
+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 b9b9ec93442423e99def9b2c51aedc955a7799d5..b8c1840eeda982c0c6350e49fae2784a599ef3ce 100644
+--- a/src/main/java/net/minecraft/resources/RegistryDataLoader.java
++++ b/src/main/java/net/minecraft/resources/RegistryDataLoader.java
+@@ -349,6 +349,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 257cfce009fb6fcd24d1fddfd8001e9b2a8ae1ae..185752185549ebd5f431932b63d8e5fea50a2cb2 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<>();
+ SimpleJsonResourceReloadListener.scanDirectory(resourceManager, type.registryKey(), ops, type.codec(), map);
+ map.forEach((id, value) -> writableRegistry.register(ResourceKey.create(type.registryKey(), id), (T)value, DEFAULT_REGISTRATION_INFO));
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+index 0d091c7b328fb70a82f5b5ded7dec61b7cd3d59a..249f0dcad04a35244da6dab837a461bb42aad00a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java
+@@ -127,96 +127,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 == Art.class) {
+- return new CraftRegistry<>(Art.class, registryHolder.lookupOrThrow(Registries.PAINTING_VARIANT), CraftArt::new, FieldRename.NONE);
+- }
+- if (bukkitClass == Attribute.class) {
+- return new CraftRegistry<>(Attribute.class, registryHolder.lookupOrThrow(Registries.ATTRIBUTE), CraftAttribute::new, FieldRename.ATTRIBUTE_RENAME);
+- }
+- if (bukkitClass == Biome.class) {
+- return new CraftRegistry<>(Biome.class, registryHolder.lookupOrThrow(Registries.BIOME), CraftBiome::new, FieldRename.BIOME_RENAME);
+- }
+- if (bukkitClass == Enchantment.class) {
+- return new CraftRegistry<>(Enchantment.class, registryHolder.lookupOrThrow(Registries.ENCHANTMENT), CraftEnchantment::new, FieldRename.ENCHANTMENT_RENAME);
+- }
+- if (bukkitClass == Fluid.class) {
+- return new CraftRegistry<>(Fluid.class, registryHolder.lookupOrThrow(Registries.FLUID), CraftFluid::new, FieldRename.NONE);
+- }
+- 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 == Sound.class) {
+- return new CraftRegistry<>(Sound.class, registryHolder.lookupOrThrow(Registries.SOUND_EVENT), CraftSound::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) {
+@@ -234,23 +150,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 f75d73402cf633254fe1ef4e919f09db48165190..5ae8f646083fb580ac8d28fcbfe8ed2208b45d09 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()));
+@@ -431,6 +431,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();
+@@ -2754,7 +2755,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 091e11934bddb180f0b2e51efb3921c62275d41d..12fe2f8d0dcb715545e071023490a32125b9c4a4 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 61d617d4abb9c9cf5c711459aa98c8b173597a9a..c9b789c2a904c2caff516ee9aeff4a6b368766f4 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
+@@ -220,20 +220,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) {
+@@ -300,15 +290,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 d8857a05858585113bc7efde3416748effb53d01..41c38b1b6d25c7a7ed08d70b19f5f4a70fc2df94 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().lookup(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 bfec6280e8b753a29ad2d9eb88808beb79ec65ea..b717a5ffa567781b0687bbe238b62844214db284 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;
+@@ -73,41 +74,40 @@ 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(Art.class, Registries.PAINTING_VARIANT, CraftArt.class, PaintingVariant.class);
+- register(Attribute.class, Registries.ATTRIBUTE, CraftAttribute.class, net.minecraft.world.entity.ai.attributes.Attribute.class);
+- register(Biome.class, Registries.BIOME, CraftBiome.class, net.minecraft.world.level.biome.Biome.class);
+- register(Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class);
+- register(Fluid.class, Registries.FLUID, CraftFluid.class, net.minecraft.world.level.material.Fluid.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(Sound.class, Registries.SOUND_EVENT, CraftSound.class, SoundEvent.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.PAINTING_VARIANT, Art.class, Registries.PAINTING_VARIANT, CraftArt.class, PaintingVariant.class);
++ register(RegistryKey.ATTRIBUTE, Attribute.class, Registries.ATTRIBUTE, CraftAttribute.class, net.minecraft.world.entity.ai.attributes.Attribute.class);
++ register(RegistryKey.BIOME, Biome.class, Registries.BIOME, CraftBiome.class, net.minecraft.world.level.biome.Biome.class);
++ register(RegistryKey.ENCHANTMENT, Enchantment.class, Registries.ENCHANTMENT, CraftEnchantment.class, net.minecraft.world.item.enchantment.Enchantment.class);
++ register(RegistryKey.FLUID, Fluid.class, Registries.FLUID, CraftFluid.class, net.minecraft.world.level.material.Fluid.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.SOUND_EVENT, Sound.class, Registries.SOUND_EVENT, CraftSound.class, SoundEvent.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/0465-Add-StructuresLocateEvent.patch b/patches/server/0465-Add-StructuresLocateEvent.patch
new file mode 100644
index 0000000000..6f3d0638ff
--- /dev/null
+++ b/patches/server/0465-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 0acf8b62ddb5e005f8f861558934e8afc8673725..416b1afcbab093f45900a4d55708609ba5a7de7a 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
+@@ -127,6 +127,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().lookupOrThrow(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().lookupOrThrow(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/0466-Collision-option-for-requiring-a-player-participant.patch b/patches/server/0466-Collision-option-for-requiring-a-player-participant.patch
new file mode 100644
index 0000000000..c5dba4f028
--- /dev/null
+++ b/patches/server/0466-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 341cfedfb06ac0a18b8cca1df5aa4f72b1455737..e556788a7c5e38c6ed293d566f75ef6501b7464c 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -2048,6 +2048,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 e2bbb4b519010cbabc0796c5b2f749b4fac32bb7..8a5b3ebc875e5c71ca0d57a52e01b74cfee408b6 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 d01a588aea379c962dd63f1428f92cf2442f4d45..7e7a78ebbf128f7d29cf9eb349f25230c9c7d5d0 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/0467-Return-chat-component-with-empty-text-instead-of-thr.patch b/patches/server/0467-Return-chat-component-with-empty-text-instead-of-thr.patch
new file mode 100644
index 0000000000..20c17b39c0
--- /dev/null
+++ b/patches/server/0467-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/0468-Make-schedule-command-per-world.patch b/patches/server/0468-Make-schedule-command-per-world.patch
new file mode 100644
index 0000000000..3ac2f8e3f4
--- /dev/null
+++ b/patches/server/0468-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/0469-Configurable-max-leash-distance.patch b/patches/server/0469-Configurable-max-leash-distance.patch
new file mode 100644
index 0000000000..39054ac60f
--- /dev/null
+++ b/patches/server/0469-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 4692704b58d1b73a45f1d587782dc706da94a6e6..5e67402340a81a965b0b3af88307d36500c2dc6b 100644
+--- a/src/main/java/net/minecraft/world/entity/Leashable.java
++++ b/src/main/java/net/minecraft/world/entity/Leashable.java
+@@ -201,7 +201,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 144ef3cc71bb265646e48f3a5f10d971b5768154..9c6e2124da7c2358d2d524c52d3e77422f15f6e5 100644
+--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java
++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java
+@@ -101,7 +101,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();
+ }
+
diff --git a/patches/server/0470-Add-BlockPreDispenseEvent.patch b/patches/server/0470-Add-BlockPreDispenseEvent.patch
new file mode 100644
index 0000000000..f2725046a1
--- /dev/null
+++ b/patches/server/0470-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 d915ef1030728a3f6ff303977784097b712238d4..6a8e0df7d0150ad8dbbffcd5f49c4623a259e680 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 696867479e74c3c94e4131f2bbb97c857ed5e9dd..6d1d3451054af05e2381d70d71b99869f37c0901 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 @@ public class CraftEventFactory {
+ 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/0471-Add-PlayerChangeBeaconEffectEvent.patch b/patches/server/0471-Add-PlayerChangeBeaconEffectEvent.patch
new file mode 100644
index 0000000000..f8164b3839
--- /dev/null
+++ b/patches/server/0471-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 cb063d6e7f4d34eab3a5f52b11f671e0c99bbd40..c257660c79516a5919032b771fc3ac9575e9db9d 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/0472-Add-toggle-for-always-placing-the-dragon-egg.patch b/patches/server/0472-Add-toggle-for-always-placing-the-dragon-egg.patch
new file mode 100644
index 0000000000..0b28271dd1
--- /dev/null
+++ b/patches/server/0472-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/0473-Add-PlayerStonecutterRecipeSelectEvent.patch b/patches/server/0473-Add-PlayerStonecutterRecipeSelectEvent.patch
new file mode 100644
index 0000000000..66c297aace
--- /dev/null
+++ b/patches/server/0473-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 46d6bc61ba54ab23eb35915073cfd5a3ab0111f1..74ea3f0154733ed4f096d88403fe011cca663e02 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 = () -> {
+@@ -153,8 +153,34 @@ public class StonecutterMenu extends AbstractContainerMenu {
+ return false;
+ } else {
+ 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/0474-Expand-EntityUnleashEvent.patch b/patches/server/0474-Expand-EntityUnleashEvent.patch
new file mode 100644
index 0000000000..a982255f45
--- /dev/null
+++ b/patches/server/0474-Expand-EntityUnleashEvent.patch
@@ -0,0 +1,174 @@
+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 e556788a7c5e38c6ed293d566f75ef6501b7464c..a18e716a8c1c6f1fe23dcd3f4da033da95417e22 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -2715,12 +2715,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
+- if (player.hasInfiniteMaterials()) {
++ if (!event.isDropLeash()) { // Paper - Expand EntityUnleashEvent
+ leashable.removeLeash();
+ } else {
+ leashable.dropLeash();
+@@ -3690,9 +3693,16 @@ 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.removeLeash();
++ 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();
++ if (!event.isDropLeash()) {
++ leashable.removeLeash();
++ } else {
++ leashable.dropLeash();
++ }
++ // 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 5e67402340a81a965b0b3af88307d36500c2dc6b..68b869c5d76aeb390a05b053eef70486bd4126fd 100644
+--- a/src/main/java/net/minecraft/world/entity/Leashable.java
++++ b/src/main/java/net/minecraft/world/entity/Leashable.java
+@@ -184,8 +184,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
+- if (world.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS) && !entity.pluginRemoved) { // CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin
++ // 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();
++ if (event.isDropLeash()) { // CraftBukkit - SPIGOT-7487: Don't drop leash, when the holder was removed by a plugin
++ // Paper end - Expand EntityUnleashEvent
+ ((Leashable) entity).dropLeash();
+ } else {
+ ((Leashable) entity).removeLeash();
+@@ -220,11 +223,20 @@ 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();
+ }
+ // CraftBukkit end
+- this.dropLeash();
++ if (dropLeash) {
++ this.dropLeash();
++ } else {
++ this.removeLeash();
++ }
++ // Paper end - Expand EntityUnleashEvent
+ }
+
+ 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 b3c27cdf7d336404403fe09553080835f2d3de49..7d3e165bae2c00737019689b7bf1e74ad2517270 100644
+--- a/src/main/java/net/minecraft/world/entity/Mob.java
++++ b/src/main/java/net/minecraft/world/entity/Mob.java
+@@ -1612,8 +1612,15 @@ 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();
++ // Paper start - Expand EntityUnleashEvent
++ EntityUnleashEvent event = new EntityUnleashEvent(this.getBukkitEntity(), EntityUnleashEvent.UnleashReason.UNKNOWN, true);
++ if (!event.callEvent()) { return flag1; }
++ if (event.isDropLeash()) {
++ this.dropLeash();
++ } else {
++ this.removeLeash();
++ }
++ // 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 9c6e2124da7c2358d2d524c52d3e77422f15f6e5..45a340fddcec48dfb796f4515a500452adb30c59 100644
+--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java
++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java
+@@ -102,7 +102,15 @@ 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();
++ // 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;
++ if (event.isDropLeash()) {
++ this.dropLeash();
++ } else {
++ this.removeLeash();
++ }
++ // 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 d4955d4174e5c2920d5cccaa4494db2c27284660..d06ec03ad16630f2dfd81cf9f32542bd1c2592de 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;
+ }
+ }
+- if (player.getAbilities().instabuild){
++ if (!dropLeash) { // Paper - Expand EntityUnleashEvent
+ leashable1.removeLeash();
+ } else {
+ leashable1.dropLeash();
+diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+index 6d1d3451054af05e2381d70d71b99869f37c0901..697c69b60aa45b6a229f3bec77dc728e50a895ce 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 @@ public class CraftEventFactory {
+ 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/0475-Reset-shield-blocking-on-dimension-change.patch b/patches/server/0475-Reset-shield-blocking-on-dimension-change.patch
new file mode 100644
index 0000000000..3126d1c539
--- /dev/null
+++ b/patches/server/0475-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 6344947f445e1da19d21de67a51b88de65eceb7a..f046b8e6307079878d94eab615a5463a807e6c6c 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -1604,6 +1604,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/0476-Add-DragonEggFormEvent.patch b/patches/server/0476-Add-DragonEggFormEvent.patch
new file mode 100644
index 0000000000..34506b3c79
--- /dev/null
+++ b/patches/server/0476-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/0477-Add-EntityMoveEvent.patch b/patches/server/0477-Add-EntityMoveEvent.patch
new file mode 100644
index 0000000000..298e088eb9
--- /dev/null
+++ b/patches/server/0477-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 d6fe710a4e3784ba38776a0496eac7fa702aed88..b28a2756429270414f8ef89539f91ce0595d13c6 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -1647,6 +1647,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 e56f9e823f9460854c6f0f2f05422136c12aabba..de948bb8903c48e0431c1a46f5aac4fc1828b581 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -230,6 +230,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 d7e5c2d5935a86bab9987c36df8e1b957fe90ba0..2d3f418b5a7a5414ca5be8525146e480add51635 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -3638,6 +3638,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/0478-added-option-to-disable-pathfinding-updates-on-block.patch b/patches/server/0478-added-option-to-disable-pathfinding-updates-on-block.patch
new file mode 100644
index 0000000000..dd3704bba6
--- /dev/null
+++ b/patches/server/0478-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 de948bb8903c48e0431c1a46f5aac4fc1828b581..dc2b8d8e62cf1405191aa4fc8d4fa548c7249032 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -1363,6 +1363,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);
+
+@@ -1404,6 +1405,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ }
+
+ }
++ } // Paper - option to disable pathfinding updates
+ }
+
+ @Override
diff --git a/patches/server/0479-Inline-shift-direction-fields.patch b/patches/server/0479-Inline-shift-direction-fields.patch
new file mode 100644
index 0000000000..e47ea8a571
--- /dev/null
+++ b/patches/server/0479-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 acecb0e9df1b157579d2b438abf1568d7672b570..690e1d2394e68356c56a39ac083cc53ee0388d71 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/0480-Allow-adding-items-to-BlockDropItemEvent.patch b/patches/server/0480-Allow-adding-items-to-BlockDropItemEvent.patch
new file mode 100644
index 0000000000..7b84a8ac96
--- /dev/null
+++ b/patches/server/0480-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 697c69b60aa45b6a229f3bec77dc728e50a895ce..49efae40345fcfca8f80fcc541dcfde1b8a8b07b 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/0481-Add-getMainThreadExecutor-to-BukkitScheduler.patch b/patches/server/0481-Add-getMainThreadExecutor-to-BukkitScheduler.patch
new file mode 100644
index 0000000000..2f143cc0b4
--- /dev/null
+++ b/patches/server/0481-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 20760e08b3d9aba86969b886b46deec5b125bf1f..1354ccfbf525e5e64483ac5f443cc2325ba63850 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
+@@ -633,4 +633,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/0482-living-entity-allow-attribute-registration.patch b/patches/server/0482-living-entity-allow-attribute-registration.patch
new file mode 100644
index 0000000000..ef6df5b4ed
--- /dev/null
+++ b/patches/server/0482-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 b3707ca404245ae048ddba4ce2190c0801b474d4..fb967ac7b3e7828301f08a7fe9b039441cf7da30 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
+@@ -162,4 +162,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 1ca252f36500322d56c0c12b6ec80c069214c0e8..a4be1eea356ba99358d707381df70032ded42140 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/0483-fix-dead-slime-setSize-invincibility.patch b/patches/server/0483-fix-dead-slime-setSize-invincibility.patch
new file mode 100644
index 0000000000..494e56127e
--- /dev/null
+++ b/patches/server/0483-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/0484-Merchant-getRecipes-should-return-an-immutable-list.patch b/patches/server/0484-Merchant-getRecipes-should-return-an-immutable-list.patch
new file mode 100644
index 0000000000..d40a7cf8d7
--- /dev/null
+++ b/patches/server/0484-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/0485-Expose-Tracked-Players.patch b/patches/server/0485-Expose-Tracked-Players.patch
new file mode 100644
index 0000000000..5e7f3c585c
--- /dev/null
+++ b/patches/server/0485-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 dbb1a52b1e5871bbb1ccbd300b8edb9aa0f56e49..78afac72265e3f586c0203951b8237832fb7c6fb 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+@@ -1065,4 +1065,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/0486-Improve-ServerGUI.patch b/patches/server/0486-Improve-ServerGUI.patch
new file mode 100644
index 0000000000..8dde1d1a9b
--- /dev/null
+++ b/patches/server/0486-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
+zcmaicV|1Ng^k&Q(Hn!T>_Kj`Zc4OO48rwD-G<MS1Mq@Vi-2P_Pf6b?v4@vGy-W=|C
+zpS_>w*(Xv_UIGaL4*?7e3`t5-R2lSh^xqd84Cs4}W^FDQn9zijsF13M{zVR~<`0dB
+z;hww3Rk_uLO*yyZ^N(arMN#SjFcHEi60E_fZug<B-Z(hc9|bGkSKT99oyHP$K-u|n
+zr9jc`PyX#|q=B419>`IjtJ^LVtno=lKj+Jze{_WszRIN1X*HUTCH>C_wc;+D)6YYT
+z*RWmTUi`Puu_Uwkj<o$pPrusj%r}Dj%{SY7MbnC{1HmJy;yKgqG6L^Ez8~9WV_rtC
+zkBu))Zv;Ih0AviFcp(<NpM4Iud9PHSRtyW*Q9aG$D&A6MQR80UdQHb@G&*iYdL;l6
+z&*F~9IaLf)qv{I13YG3pb-Q9r9@Z>6-qwu_Ue*kO&$%=o%J?6*rej_Ock3znkGIb6
+zWm&yS2Z9LS7slFgUx+?ilDgQBdj7`ruw|IVzJ@wV{&tD)G@SPTMW@9Wl5lcsuU~6`
+z7raw|%Or|@P<iuewhar*LG$fv{PV-rumN!^YF+sriw!F(vXddsdC8crID6#HGQV_Y
+zQNMQwU{~ee<Y?4p>nlh`7!!rA1H$`p;<B)P>zz}+92Tp2bFmKDAL`nrC>)<{qBHso
+zvJ6|o^vMxL?frh4XZ`3WdH7<mM;zSn%>s_NI0p@{EElbnX*!yp;Vtx&K&w$&to`sW
+z79>enm;xWhu;ZKKIN}-h!eBK<r?3Weu&JNQ#rPT>ZM6j$9~*Q(SlE*i_bHS0o#tPY
+z5-j+ww|x>h9%`RLUixM!e%f<G!vp5vXXFX8kGG@oBs1697je+0Nx}#TpoAWD*|IYb
+z-Ssx&KBQO74>0qVAe5GH83X6?!#^_j-M@lO@*-aD%NMF2;Hg^Wgh@}elrPA3o_&(-
+zeNyws4es~%;K1o+pfG(Z!G-nFWzl7)ejRNxY?M~uI=I&MYuz@4>GLH*ptjlQJ`LYr
+z*KIIVzBhKHIDwe`X2hc@gsdjzXxX%b<_#kc$vIHFi2)-XM1=fs(`g?0)M{lcJXwp<
+zBgIdDXM&n-=+_%;1a?sE$oeN{r%w=8tFfAl<Qy{<_vK)o%n;sjWBhI?x8=90Lxkg`
+zxkx20^#e}g0<LSzIxbF{Tmco72&OwY!+=|%_J|fBLX?~-X)wN)AGYA$bh|T>QopAk
+z%wrVN=r>)oZ0w7^M~Xi~qp6lEaABgF<Z2SwJ{3&bce8KBXsfF_q3EEvBsuR{Xk!fV
+z#)f;iYiQ^(Y|-yJ2)^R36EIGnWJP5LR`fjt>(ck7V3Un;@cg|ODuD7@fw~OZ;^TQV
+z$&4AiUj}-4;o`6JV$Y4C<S!r5P|PN44CIQ+sDKy+`zUdEn?bMs=B=5}O}j~Et~>2G
+z8hVweUdzl78hW<t^KJYb^Pi*-y`5~j=yosxBRqeq6FN})Y2R*>zD|&J_)oRr2JdJP
+zA&lca);^P(q@hQb9-kqN<EgkX9B1Lg0x!^RwSp0@(*X;oZOCftvUGI4_lBx=xUhLb
+zo3nPmzU-m&+)f%w8l)wW0-YsCBels@kMfp_Lh9??mD=nZ*zvU*Wd{-d!a#GMo*nMA
+z=lqHGJ=0_2?8Y-csf@Z852{7hrkD&)J1n#!Wx(38*yZz}JzrCxB@jR9tG6LU{M;p+
+zwQ)kSjO%)r1@%(I`<3J)ARrMR`c8)=f1Lt6$3>XVo9An7Q3NoAtyRQw-@JUDD$o<f
+znMRf7r(dF(J|Ki=lKXJy=}$IiE1fIb0^Ym|0TW#X<*B86mU(PHgf6oWN~L>luryjE
+z3{zzbZhStP-K;xw@Yxf-B=4h(p=4f`k8p2DH$>qQLPR!szD!2|vJ}J`C6=EoRwG^+
+z;`ZDv1SGVO+?IqSxpxSM^_V~@2E+~dZQdl+oz;TP1MX+XXwugMy?Z5AoZ7#R33Y@T
+zM)w4;9L0szO3>6i#4fV3q49@wu&`zcvQ!d8!m*dpn&7pp0Y=;QbiyOzhC7)Ki7tDt
+zXaIqysWqx53ZgHlO)|YRDG**$7&F{0a8VEECY`3;yx)F>2;4Xr&gC;Iqiqx;orWkF
+z8xk0Ty-mK&z`^~Fbs#S;;Qd@1ZFJh4R`+H>Wx$xgn>^oka;w9~QfR>rS7lYHG?D#o
+z6Jo`Qg<Muls%8W(@Bn4~pcP6}Gk0gmCKDnOK3h(_4kg0j6MLXiCxDBi%<rTP>_-DP
+zX@kdURs~L5?afF*73QF!=HQ?vIysP;FNCMBfA*}*&%$eDHh5L|y~D=C^v8(wdtcYZ
+z)8Q|56BuZ~3~KpF-oKg|5Uf@Ac15Z>sP<9hpm(E>^cgr8dMxGhn7mnWA+JPK+EGR;
+zCfK+V1&Xi1M6CUFIA+oJqr(aF3W_=ph7h;IVlqq&xJ=d(CqczQwL>f*A$gJW_|iZw
+z&>!^cGyI)UH(_%jFMta0ci8K;?^D#C4_`@%@wP6R4qvs8y@ecdj|*ia7Exg3*BpG4
+z%Dqav(-_hWolzv04-3Ygs)Z~U$`R?hQq2Is2`RWS%z4?!GF2CryzMjCEFg_Y%K+yz
+zG8tm;0X{;XG5?BBT|pMZ296(fGUtoF_$Ryrso&s;Cc!g3a;pYOn-tjPvW+1)iAQ)I
+zaPyG(wl0MZUqz_Z!4+oEh$t>QIaiZ+J1|fQdfugliOCAg+6D!~3<-k#gA8N#Rk3@5
+z&u3Yevetsi3m`sm2Ntt>FV(PfME~wR=LFu+2@Noy&wr###hgP3mjy&H03re#97OQ%
+zsZ;NtktNoC?s@G44<dmW{f5jWvTyiJLH^yQs|kPo;mUvH?YAxCAg|ea`1SCn%9nW+
+zAtbqiTV^rUI1*#_DYehL-JvZU|HMeqE-qAdM%s9}lUT3VidcPhzcs{J{1Pbv9kWhq
+zc>Num-@G1zw*?jMf)<DEgHaOi16Tc_GG^ay^ID~7jj9}99V+-ffa!?g&F0&L_z(A~
+zM}JT5(~DuP{Yy+if=%fxqzfhy4MOrHp2A$WEHs?v`3DSZv0AY|>dA`SWJHyI-Lp=m
+zyv8V97L8$~?>Sf(&Ee27TQvEf=-_%~EL56_n`*ZRVS`=4Ka4&HGjr9P8e3rf;8BK&
+z&0s~H!Z|V-mPt9vUj?5&%Sa@;XK~`TS$ylgW4|1h&I!<9c6_zoDdR2)FLErHw%Sow
+zwc_2ZKizcAMchMvZ^6OY8)<qG>uiUt&RwA(`3@dzgihQ1MSrNi;ruq-C+?oVa@U0x
+z(>^4ei3Bedg+!LX52G(u@W4P&3sdv45%OawU(*aQat~OuEf?Hi6Zi>__qCd)nw0_j
+zvUwA_6WQ5tnFsl_AZNz8L8L*=L4?0A>inj9l&C`<n8!WYp@GJJsV7VD2F>AC71u=H
+z?bu{Q_=al@1+|F&El|te2eQB@?#+g(D(LjFx>w=0X;CJ|CQc@tuin_)Rd$KH$Y9P9
+z${MAq+Ns2`>_SLAfKm9~%?U2bK6><zn<Ur6w>hiDEbdUD#NMd$hR*wFx8TxWVY3Za
+zM&tRPhR$htT-*KlZT-SGBy4YD;6aZfAz^Jt1`=ABifztn#D_;u)2WTa-Bo^EKL;=o
+zDc6Ov2x3y<odK`qKZ@d;Abc>bU1B6gkFjv-UvyFl^(EFkIb4ht2Z<LM*J8D=dG^q^
+zMkYxOjCGDOM{O^I*O5+BWk`rv1mWOE@+P54krJ^tP#<l=ckM4=+6h&OIU&z>(*io4
+zW(6^Rp7OMxVh73mYH?bkbxgXB=+<qd2^f6f<36>TL>U^8OY>=P$oXPkGAmF?6#80T
+z+e?24uzuJC8?nCu`7)ef&Nu8x+`0%wOB9wmZ^(+|&$!T80~3uj?NRH)aNhf~#vN9e
+zem1VW#bKd$SZ4ufS0-pzoJ%P7UWdT@8yg`1+kpYLV153t;UJy~P8@7sO+#<ky&oE2
+zaTRY)D<ZM)m%mo_4}){09c-QN`>{ePIXcSgw}v2XayA<>Jxh}D)tMOGRgJY0QEJs`
+z{>aB;ssVeqKi-6L#(PnBpPuOu<4Rf*GWVk8BdM<!bAw+)5FLpck)j0P;g-XUo>Cd}
+zc^_!LU3n2YWBEk1?0<%f@MkB;t#h0%&cixNCZn@Lft$eDVl6z=l@Ga}k<7cF5n!!o
+zXet^Q3;AyG!j)+$=3U>7D5c<q^S#x_L*FxCE%0h8`d^(!P9b1&)aUD8>Ef)=<q!3`
+zEx>YMZ)jSZ?)!6EoSa3kU!<iV+p|9{t1Yog7AUs!^759kl+_ECQKgKtv<Me0u){xU
+zvNK4lG5Lj18W*ZXoYw+7Q{~Io3%NJQ!=lyM&XLM?;1=x9;!=Y@!qr8739mD3ld??W
+zk}v8<+^du4Lfdz_gYEgeF&99Myp%KlUPme7^F^!q+sB0B0C>3W2Xn`K`Pq<Ha|d)N
+z;q~h1DC+D-+Dz}H`Ee}lrc`+QC0$^;y5!2lQxMKotyYaHh3Dh`k6e%Q%VuYLdoeFJ
+zH~ZfHz~PX7I>R|ML`Ju!A)|K2`l1><Ha2;a0S_c>ErJG>o*qIC72B&jHYe36od@P!
+zi)qQ9Y7g*>N;Y4;sSLlPxvM;q-Tzw2m;Zx=x>{mk0;Ed5zA?Hb1FrDGc6-;m+iSFU
+zc22aC&R^-iyw5vE$D?GWWo7A5o@@>d3_uD92sGM_-tlsdQ?ZbAnF4LsSxDj&0TFgO
+zFbB*@;0<;Y0es>tB&~M12_up)gRS(Ce{seFR$9$~MC8~S%gCTV+2AIiH`gndEW2~H
+z`z|RK5KuxIccy|<V?aP`0WazqQSmO_S2;aqzDxX>!;Bkm8puw0EcWFE{ij71G*o4(
+z0~y!3%z_nq1kdh3x<;XVQS{_v?Q3|H1so1Z#CL|Zm2Z&7-mTO?&1?U-oogOAE4Cm{
+z`d4o(XCnWH-J^hx&?7X^xHns&B`u2*skUy`s~w=0252bVaZy(}U?e5?u>fG!UbYaS
+z4Gz$YBX|~|U$??YUR+zxw2g5F_OJB7viI^}qx|ouEswnc0o{D4T~~|912EVr9)4P&
+zS=*@uBmgy>GC)sz_8A$Iga2y-R#LKP$zyVe7P=4Vrn@Q)Fp6mG;Nall=^07<{OPT~
+zPDD~5M}Py>^H&ikOMCrXaXjFMyNuyNg$gXaPOE4z3=$o3<OMi7)&qF(4hc$VL&fJj
+z1E&LS!gpH;axN~M{o&Ywv1H1dv$~$Wx93|NHp>Jt(guFuvAQbA?*MR;Dx}r~+zsgJ
+zzCtQ*$r?UAKNl$E39K|(pdcV17*;zU{VtG7{)QDicnC&XAit07AxkJs2xbNxkEh-l
+ztI=-hZ#0{5e0{huHk5pMKFXUdk-_HT=8j~#**>ze%L-Vq--ELbc7OqlEqqgfDL$7|
+z^zia3^m~7il#>&4bK{s6W!C%o9eQ_nw_LRXoq&)qk2e`~Carh!_+@C+^?4E@nB?8v
+zrP(B~aF_-3_5wx4#3EgX2f|T2iDX6dBot9e+}zxz-+7y;fop?^#LWumnJ%(ER<|F>
+z44(0)x_-m7iZI17bV#w5<;|{V>IZ-R+z|XI2d!L0M$z{_<K@~dl`C(tpC1Y)Szw}R
+zq&}HG+Lrj-8Y`=$N~2}?8b|l9dGHv+zjnS*Kq`-?mfB^p+k|=E9!9dNC=>~PzI|b}
+z_>I9TkwT-USfkDE<T3o2UFH-DLvho7UH5}kxNU*8zhI%MKhH(OJ-i?BY$W~-^|55)
+zWa=r&p}q^d3PUbX_6!#<7Dh`;%jaA$@p7{>yuoB7YJe7^SUeW*JCd>d31w)Viag>w
+zE)Hcnu_U(A@CEh^w;UM0IVsDf+yNUB)lCpiM=a>2dMS<By}F7IgoNFjDG)Kn@=#YA
+zK3S^4|Mhywi<Y3>Vx95URpuHBLGh>h8fgM&77%eeba~6*@>lA8=;7iEw2QP4d^IvP
+z8fpiWc?lq5kxp*C)nS|HY^i2ov(x?A!{1u(mk%xyJ_nmAsx{Zt=LV=Ta0-O}2|y4O
+z5yIAhMw5|xp<jAS{T1goL&?&tBz5keV*#2g$~m*;O60P60)&DePvpc$(-fJzJYIL@
+zMljJ}1>3lvw|Ps$0W*KZd^Wlj=W@{AaG=^es3_){Y~Jis`IYYiWN~ho|DLil1qRD5
+zN6xAlvXG=U-8`VKVHr!k-;5Bi)EfnJRTtvY$;jR$#e%~lxMV?xboY;JA{IT_^y}D0
+zw1mJ8tVoSO-(}a<iGU($L_s!qHr~lX-p3`LpC;{qQD>bsB6M8b$Zqe)Ok0$OkaA#I
+z48@e8TAlv;PmB6dbP|{7<%qt@Ea>I;PRL4)=M`_G!A40Y$Xy1Mum)I0#!3<77H4)u
+zI6c{)TUsy&o^*@2H9Bp>QJA#S8$`zN?+@z^IIQL|VxYEQfVw~Oc}Wq!FS`G2T=aDu
+z-DMYe(1$x=331oN(i#yV%?Q)lcY`}FpGRp*74@@$fX%pE+dAGOh5QRhJ&mcaXOhk4
+zLi_pirw^Zws;d9n^#IE8T1ypZDX|crNABquU?iL2;Ql%<xZgIthm*+Me<Sv144IQh
+z+y#X35h0I(f?Jn7vV3}cbE`r@p<fw1A#t1bZc1YTd?oNP`S~O<1l*vNz*T7GKk&TY
+z>4Vg5cNBt}OJdbLKnEi|`g2q%v70%e<MWo{Z54-bRg#0&`qxlWo}1oq$2e7Jg~<kZ
+zzW&tUlrY9b8!wMZ!)FG}oq-Cgt7G(fIFBUcbt4fJ%!@avinPuQhDC|WkXbV}j>M&7
+z5gdFef<yi8=NIQs=cn8~Fk<)<8{fRekO|}3F~KUG-K#19**0R4d)JJ>u8Ix3n54MC
+zW40SGT11ajrrm5AI24T?-2$|VMsU%VX}AMmt>Pr~B}#An{>%QG>_1FQYV^)CExzx2
+z&7E_9c!fpiCLci|F3H*eM2DQQRtQp4>V2&#1RP=KX3ZVw#OXuFxj$VDmM<G<u~~j<
+zr)S^na7COim80bdHj8W`F@;C%2>&HQD{*dc7301976VQyI69%EFvxxn>qC&L<E!j`
+zIHGshQ9G04HH3kRXKn^}+y*oVrx5jxdh0M60xK|Sd)@G1l7$%@oa4CL9SGkajQnDZ
+zq^Mqq7mh3yOT@pX=+DK)rD+NiZ3~dhN|m0JtBX_NP2nRs2(SK@;dIooJ>o-`%ImvM
+zCv>AXKPcD26Z_;m`1pw)uF6Mp=RnShU^yM81!?jbl!v#-kSa#RLhSOG0?yp1YB6Jr
+zW=GrO|0zIRSHiH?DYiO+$EpdMkwz#4I6V(J12-W0+dAo4J*?nDQrFI<*}a92Y%1bU
+z`RC_4<V2ee4eMNw8s%FkpJh5UxnvQ!odmE*L6UqK9@Z+6xHczFTyw(v>tyg7>R(8{
+zA8*g?PWv##WoF+p0bJe>whg#+(1_+A+<aAEfj?>)9HS$|n?k;(r=Le*vR;57rn)2&
+zEkD8KBSZm#3Drt?t!*#s#>0+yUNysIKRg=t`KSOcSHieiUP0z8F_$tZ(ciPnq_o~@
+z%-{zh<J*Veq)ewvHPIm<P)@9do?q;~Z}j;4gSzD8<V-%gspfzg<TvK(YEGM>bs{i7
+zt~8q8%WO|MF(FE_y<dA>e*bl_-@NcA!S9$IMb6x0`e_oNF!hy5a)<B9TQy2D<cFAc
+zF?41xX{OMZmefU3+w>H^H)5)t(}ek4a1Nc~FF4@f;5aO%aB&3O%B8NuMWWCzYb`d>
+zQ-&3)G|5M|pzcLy>pA(p=?3&XKn+v0^`HNsS?M0eb+60BxF|&Y{?>MI^x``)Vp}1V
+z;<0N$BUc(0=p=y>zD3k<q}Y8+AcCcIhZE%FEx<NLcJ~PH4f57sm^|KipT6?7YhrO<
+z5n4J2NHz)P@VNF5$KnrBFZ`vUnel)AgDsoGqc%y9n%hT)4PFSW=~V|s-Gnq#m>_I~
+zMC>T|r<IzMPZsUpMmccM`~7q^-gBxE8n1Uoi@@k@#WI2$Y*f#I7`5xI_*1MgU2N2@
+z^eG)oSYCiMe_2*N+|r=0Vu@%7?B{{Xx;a?lDd3cv9kkEP*W;ZaA8JRpl=-jMJ%r^M
+zCe<r8%uD7nt!zE<kwG@ud2YkLV(WH~-pw~fnJZrqo`&ZGr=v%-HmRL^lg5w%)?Xdf
+z8GyHmjcJ}p(SA=9aPzv&i8wZs^1@?kH(d$pau473%lc-?eyx*})9B>n!T!wN%lqT@
+z&Afsj|04$m&CH2M?F|6yeqb+e`&JWTP^~~z(;c>5;z6RuFKe)%3j|YzeZB9c)5E08
+zvX9?L9%?PT7Vu(RAIXR}s*=I<uRwy_BSL{QL;Eu-Qa(o`mnTNne9Sa30EPPEJt+@<
+zp#ohDc&Gd*U!MV!j5B~M)TLn{`N4eLPTO+kv$bEVK;t!H(BsE%ztuJNxvUZm<n?`V
+zX;2AC&F&+U382#5nDIK+u;g9D2ceKb>*@Qp<*vA{&7B2uwdBH$_I`33U5di9weG|3
+zx-Iy`1L`R>G-q<+w-{f5qc<7ls}^cT4Y^Qi+meHXFIDgqkt0wpdBZGY?LB+q9&o`T
+zd18L5%R+44Ml^UNbEw58BXP#{+I#J1$;VGO`#6Grd<=RWgP+T+ktE6H^>C;%(}szj
+zK;wt^oW<tgof(@F90Mq+=n&8JLg&8fAC)T&bMQR|%m&TaFS`11YvCXVKCa{ZL8~Bl
+zX!HfBiRXgv4WYI!Z!S^;rJig#<C+@{PjVn61MRZd6~tP@hcr-a@OEY_3Jo#X{yUxB
+zCbb<x53jHQ<07TdnY6iL20clAriMFMj02|lPk!CdidDvC{5bvm4$t3wF6Em!UD54g
+zDwqgD^Rl(wYb>!yG4Fz=zm4zKw@$Wdo`VJm=879kp$F&$uMP_qiKSB4L@SV)<o?t!
+z@b8}I9N>g55F9Rb=3ocrK>iqIRR9n!X0Do*Ldi{9M&^sg&T_TZz~>`tbXc$p%%BI%
+z#MahUA?U0t#2ZA4_41*w&52#TXU^_G4)$#uGOnpIb{Gs?Bge_xP|beH;cUSBec^gk
+zu;a`And#3j5LZ)LAL<cm7Q+mP2=~Fd!STmi<e5Z8e4wG<pEWU}FV0~dCjlgckVACH
+zq9q6%IKSam)`{4|E{#}*z9J$;s9GrM5PCf_#PW!sFXBVO08lMbOJy_uZixMCC|@VD
+zV`k3ntJG>L9lQ0{$A?tzx&K6M(;#M))7n&`7KTkT>KvjI7O4?mTa;X`81yn7WAir6
+z^Dv#2{~#3{X=5gyP*2v`3yoLJl)--n2rC2}*3n8(L~4ohHzT6QbyEu{!K3q#&p9Lp
+z?3#RrZR0JWoh5V%Au%m2?uSB&R<iQA92+*Y@+cI6j44t_h023EBCpi<I5`60E*hIL
+z>O!i99khjDd#7P;NaxJ<_f>mYXQOtXqBZif<x5V;e8$sJ4ucprdS6=76OH3DIx00;
+zr@?!2AN?pOs)?RY{8}AkNKVZJa%;%y+M^NF<4tc9%D-iY`=)tTYcBWKE<%Yiw9%%D
+zS*EjFv(hfL)a~iYFgm5X_PF5~>oWn1d5WC&hmG;&Gv(>!l)|)selJ-m-pz9Og@*rA
+z%Xl~n+gHI_Rjy513U_dEaq-~ZLm%H7RpV<IR0p~J+;&2?kV82msqT8fkP1sSj2%4`
+z1)^UjAV%_(0=dQf^t|3Rqv$6qMVAAHX%%m(_6P>bREoW=Zu*D?n%JFyy6(v}{RCOy
+z>_wu--o5bv-4rRuWG0oN3a2+(f)C6nR0%>9HdI1mB`d{jE6Q4vSf>>{@~N-bGMc6~
+zn=1MB2?XIjZuOC!s@-pN5{60UUw-L4f1L-3Ohud?4)I$4Y&#w^A*ij(1$$3|Vskv}
+z#YKCOBnHKh5QN8fd|k)wI{^HZj_1!`{L&>R(m@P^tYk*J)5>eCrio9{j>kWLDCGrM
+z*O<)utCbjQiH>aHzD!~>S<PU3pyI^|2H^|uA8K8K@16lp(bU!op*y#_y`x#B*bbDc
+z7LCa{Z6vjY3|g#Hj9@0vV=JdXah1mvnC-C=(k%WxIkMjH1PFK%C1_nf?QEs`jYDCF
+zUTUHpRm64A3!+5iuiW+nnU1zIUP;N%T?I<%OK~d}&sT$agrSxf=YC~O3^hi4ze58t
+zYrh*M$%Mt*g#V6dL?bm7a==9py)xK`hVB_Ta-nZ_kJFQw=~*NkZ)SVx&6coZl;7FQ
+zN4qWzPH870+<`J%9aos>NyzV|B?uyizaR*!v`(g6N5ks=aSqWHk#wzbQOx2Ehc(>s
+zfl`oSK+EzLOKDeK?n<u>#pu;5qF1g-8bXyN##%K`x2R14CxOh8w&P-kz4U}>3Q=A&
+zwAa>sCXe?|fR^Y+S9_jW;=!_GK`1Bc2HY6Y)*s}A##+#}239~LV&Q~wL&4n_6^@vW
+z;nGUYJ$5-C#kJr2EtD&Ty$t-H)#GyT->}39LWB1gdo%LwqR8{YbRBL*-FCEc5iY{;
+z#TpZ~y8yolNKuWi&enqz%<*)Y)j#ff)9q1ezkI|N7|zr3<o?*+;JRvZ-Y?YN3nrDc
+z<Onp!j9Mf+5A2NRh3|Az8KhKm@KH&niH`ddg;Z;SxUyCP16j;Grz-FV0d?P3g)Le|
+zos7y#E&CJ+9vSa&X1`JVNHhrwj&NnqqCPt(M^2wsW(6k!Uf|=Y$zG%w@JT7|R|gxi
+zr3+j8jJ3EnSpUKST|4`Vq!l90IE9{SoFqR+GHa1EC1bt2R5F5fF*>b=T|b>+m?)d%
+zKJ;1@L~w8ZQn0MxZS*{ew-;Ohn^Jl!+U{m|QvgB~tai**t#d>0E=CMjN*SZ+36QnO
+z4NrSN!Cd>9SLf?=!Hjh+ek}c}ND_U`vvi9(MS>7nGZ*l<Hmq_}pg^NoxPAelAVczK
+z+9v-jKscGR%3D?J^Xp3qcvM>Pm%4(7(bhfuTHod8y%;N{YO_KMV}N<7D)x5snD;XG
+zzCOH#WK2$4mAvQWFCCZW#F8TRInJ+=$6eR`V~dES6+!6-=6lkVCHyCW^Bb-$@=b%3
+zi%hxQwAp^EOp|zR61~UikJsM89qE@P3@X5J>+K)hO6K`Z$80UqhLV&|mVt3wQ#G4H
+zi4>T}s*jr9pkN+B@=LbuMW8^kzEFQde*yOdnXiUws9u#OD8dYzm?0F`qCm7pBCNNz
+zOJB@PR!5?2&9Zw_Jg~i=TwmStKiYq<aCxk}5?tZbG5<T2QE@w{`v9b{e*GpE>1_@$
+zZKB*^u}y2o({7rV#Nl+8<Rdkl0a@$MpN!_-&_Ccw-kxLT);QIY%C|Au!%Igfx^3nY
+zqQW?uNhGyO*g%79wi{Xl<pL%^<L*Ucm}hQ29FcEt&?fH3+ltiY=y5&ppGG-@oEz4J
+z7QH5KxK71nNG<)%_=$zL><i?GEBH?(B40WD(*2LZ1LB`N{Ao5PmAglN&FZpl>$2T5
+zthMF3X`+*;4Q-~<qaR}9Th9vMz1AXL>&-*4NzrU=7>#}h=jB}<^tsAch7Ac~Vq;V7
+ziknpCHOP}_P8F&VE%6e`WG~EVa?$ra`knKZrYWbIZ_w@4vO+{B!(Pb&!YhY8pCfe=
+zjxF8x>Zh3;#gw`fu})grVJcf=Ohg_<xsdZ&$;Db2&61EKPttRh=b4(sN_y`B$-^iU
+zbaR-Yb11Loh#pK7^C%^llk_r#NFww#waCKFozWylT7w{l+sUF-C2bd{Wnaa2cZe^u
+zn|G4%4HN4LI(1E&Cy+D;QqbqgF=GjrLR+E06_dwL=4wv4Tj*+|*(R0fY_3G+nX##|
+z9LQLMOV`Lu0>Xc9m?(57$!NXQ#N%;Q{V}EjtmA$m<@Ie2(h2j9T2Xq=0<2R#daW&$
+z85=lCIqjn+?h$SF4u|?#DOOKg9>2c{9GSdlh{<(WR;Mb+bxH>u95roevUiqSmcdG*
+zEL`{Qv+mA#hjLxuC*l?ROBgDsPYkDNU%;m09$2^ni=SVA=kS_<QrbUz1Y8%cg`w>)
+z_h->URCbhQr89T-a-Gg9Dk?P`CT8-=f%@A28AYMmma&Ks#DNDsr^|eI%nHBQ0Nps*
+z<{@u^G-9krSD|^{Vm?_nRkW_T!;E*n95To#4sxn;9FH2W%&T043S^Vg_Bk^^&J9*H
+z=-^Zd6GYUG(CMkA?hy<&4Tc5fn4$3ys+ZiGw!07qHH1zPDzAJY;{8Oj#B1-LTAZ>D
+zKqX)c%j0#o|H%z2zdkxYKaV6<&nEMgP`q%2&v+2dsa++rFeWoOnf$VkCAY6|8|kw{
+zdwe(maC?oeGlx#HVClH?)W&QZ`+=l3PIeQ%9cb~nWxJ9)YD|MPt`v?0-3bMcbZ<2Z
+zG7xSnH{QoOr#C@?R{C$168|JMfCxcPAVuEhewgQpYO@AfbP3Fw+|Vi7h~L@$6ydj5
+zyf7_h9Rp$0Gii0mkT9xddqw>hIVCXV203~$D~swIj_)TV=zX)@-tK6Hb66mM;EywH
+zsMV;{!i^8fva<OFy6>e3b)iz7_f6$4yU2i-b%Bh|o@eU2$RD^G(AtWlyl0^8dxd<9
+zCi_xU0%&wFugtmc%-uOk=xMY?lR%{7BQRZ~b8}1<=DQI)v2*#3|70VNVV*?SK4O}0
+z-HEICfCoyTwy@{F=Ac>4KISQEgQLDcj|>j}h<?bSz+1B0{-w9kD!eM3*<Z37%?4E*
+zkA{ZE<$MVE{8K_UuE}NuEQ7P^4<ITksnw<(11+kf3MpfIy*u6n*}`3yO2>zn(*RSn
+zZw&u6!^Z2~7ae&u`+{IHYm_vxJJ@RRZ!LoCjQ2ecK6E;Aqey<dg6j^l0`!YnxYi9$
+zM6LAhrXuv}BqgdM(}PZ8CZas7EFSpef@p;1<$!_e)*`_#yxN-Rs6oNz6|Hvb!y~|q
+zh|&aXdTokY2g!RF%s;~-*j|$hW4@1<n{R1pndLxAptQ|@z=;7T$_-oy6r5g`(6WW7
+z0~Lg5P%%i9;@gCpDpoF$H4@@H)CjjK;d~ijGr8!04az5G=lEzh!m;dMSOO20Zv`}Y
+zr-iB|ED^!%pcBHh?<gu=GhyRLC1tsuIE(YJXUH?a_pCjE<xhHzrjd&pxx`;jQzh5;
+zl9Q4KN4`!eE6v~vYIt=mO!=-hn!UAAu}eYoAW6h3plLh*H$37JSU(h+uGkpx@7+$Q
+zFHJlY-*f#a+nGt2y#)horiF~LDlif$em(#7hPWT7k)?Nq{j<MPS$NS8i1>JZxfuAC
+zaFBgBIQO4DawgA~vN)BCS%`;S38kn@9kWOTMq)$V$+z&4nDQvH*{(1#N58$C)v2#;
+zJW|ch#FaXRBNNj6mX)HNV{_ScADWB7#Jn(Th}B15lvrI|-2<dL5!1=&wWue31zOTq
+zw^i}lLoabQhZfQf?iUFP9Z5m3!A3{9j?q)ToPigJcwL-KMw|?59r7;lq=EA1Xyn|3
+zKQFEpiW@9}A<zAO?vr_<V%};_IxKbySSVeCdLh1TCD(W}kZUFmMeb{5>fj-=SL1AY
+zQrI&y#`tyxRIyenc$G7)m}|d;5&h;8q8?ap1~7v{vEXIAhojO|^XI$6=K!f+>;5yx
+zJJXiq*Z?mW;Ak{?4<=)9$$a@6Q*<UTmpguGcnDIPC0WEYN#Q;#Yxy$|D3``2G%7BN
+z0Yu^RQ7okX8CBPqG!lDN%^_d=COePPay&UYI#6#@B{KaL`8fF_auJMF1vvL@@Ng<C
+zI<Vd6`Flf-AW}D7j+&*Un2E<)hp>=1_%}Nx&bGA3oqS%{I)k3y{#DALAzrPw)h(FU
+zj}8a8Xte($dBp<ijg|@?5L~1^;NP*a?DW~(Zh!0u1DnIboQI)1jmk@=vdiYoethVK
+z2VA2EQv@N8+$L;v?}g`7We;lAQ0N7Cs45%8&+P5um4~~FV_#?}YNMf!&GB+#_IG>T
+z_ZLeg50aO#<yc2n3)}HjIAy6<VTQX8SM42|2g1dr((CMP{B(Y6qxk|d#EXAUaxXkM
+zwUwD<6NhB^T_hSjX`KSqm$ECgHu=6Ocle)oFKYFN8Tma6BWbCWiB;waOh;6`(c*u4
+zqG$he^u#%iy<Uw`Ct;c4{~nZS%#WV4@bfxg(X2g|KN3$5q}$mfwzscUhZSWBB*Pr~
+zM-+k3z<RHH>zhmy?M*+dS#c4NyP>CZSyS+OOi>@2;)lr;&A$)(OEO;kV+bz6O57by
+zyW>9>Ij2^Du|A83(r~$46%S7?Ancv<t1a_}DOz@l5HE6yFlo?8Jw?4@@8O%XR>(6R
+zJK?TL+k$9p$KMJgY}hdrTzyS}0it==hvU?8YM**7M}l@-<ok|B?D9J>W{&s26~NM6
+z#U8(RCX-=6Lw%{$D&=aKSfE%aJ<__RASP1DaZcJPva<-yi3NH#t$OuNk6wlp&CD~1
+zanJ|7AhF;l{a^)Qhr<C0*mv)OH?=aSzsFD-;L^+K4SEaqsYLqhx9tkX_6ia?J#83$
+z`$z06sIM{&fPSt1-%z9uNqIz!!`X7AZNbDv=pR>_9Bo;2ZG8=}0whx#r7zZ6W`Fs5
+zJEbvhZVJVsORu$w4Y1HyT1E4?Vka&kS*mSpBuKM>OAT~3W;g7KLGzfQWF~QJ1)H6S
+zFCOXwP_auqzKSygLBPB}EH;Q1gXb@Wm*lZWfM<8NWGZM_*$8Ze)0+^IpqCyco5T+P
+z>!edzc-RMsx%H6~4%a*u{&6!V2Xf)f8oOKEEtBAhvI#TkSv+Ago-TMSQ(2q}=S0FP
+zL(1v}1vp6Ya1@zfO!}Dq3ke|~@mmFXu2dHEQWpO$6X$;c8V@V*w>NACSkmSKF-THX
+zXc85Wu2(uhx0b@}vaeA-YhO(oJ!8ZlugSxzOn{tnI7h@dCB`UVE~EEY_ww_|qDlb|
+zQh0>qvDy{uar91x0J$!N&ch{3*B*?y730`NAZJT0IXU?T1Oo1Zc+QnB&!+ZYLh%_v
+zV;)6DQs1sEzvoxu0r{lou-yG%CgwotYzFK>vqr!e>KRehvaz@y)fTge`_wgV2*|2H
+zVl|vbxEx$3ymn~uGqN65%FYqJ<_)*Uqs49;KY2h*(Xa?Tk7AFfl-xf>irJoUyL*;0
+z19&1GQV*5Ni~#kTnaq0ymCiLjk_=0q&=&|cG{r57n*6NwV6zJl<AE{?uiy^?^PFEl
+zHL69trWdxghat&0+%;d3D%)bwcJp!RtqFvYL{}8g0Q6YuQRDUcg4GskLUHlFezjgb
+z%oGcmW{c;iGpDCy?cU95%R+Qk73F1uDmg--Py0a;zrr32XFHuef2iiC4FRw~6D^mv
+zgMdY9dT?<uc8v)5UGd`0_-us5eL?}U1d|_P=m;QXl76{#yY>5K*ED&DsZy8iEL_rr
+zgsLXr6cN9-S7dCo0TeKI3ByoGNNBIG{4b4m4=LB^FstU0B?!6TBZ1v~zn%e*Xk=B)
+z@_rySE6i<YPde}>HcIxSfbe^sRAkjZKFfR!7A5uNa|Q%HSV{);)`X_I$=Rz#g9)RV
+zjIuDE+A6IDHt@No<L%X=db;Hw`M7lZ{F)`q!D5Htt7nHrf@-5e-`j-vV+h{^xhA8s
+z$s-;kt^*QI)B|UnrciuVNlIMmjGIErd$4j48G;5;lAAA$Ev}+q;LPGdoNB5SegP#K
+z{r5Q+H?7HkfTtUE_k9@xH;}4o67QOQ?Hl585(7w&`EOai?w1T9lK$xN+LxkuSRGel
+zqy!S_YM9)(Tp?r#S;~dB<|bI?1gcloG<=?UibWT`0kG_<eKrPzOC}*3Hy088)4@Z+
+zv_SccFl?&OC^;g&?yV!ni}*NhUvPXkw)lcuC^*Zf0?cUPiE6Ma9gu1!C^&e?-3+~^
+zXl50oV>y^%sCnU|?kL3tCMU12QN7688MFeYr;%^{CT)BqX<4rY8gFNo(^2<+x6~@>
+z0Y;8%xJK3sk3si!JoTyNPRqf>i>%mkw_b{g-~}-aAljQww_S1L53kdn=uMD<c17D#
+z?H*+c=osa2w*s-S4Z~_SUsrKZwWYR;6)|&Y4rFt<B;x%HbWu)tqyir54O=xC@8VBz
+zgT4^EGyVX(hb0g9pv%V|#R4Op=lH^tCrs~K9#Hm6!z@M3QY`N@z7d4`-v-z^-t=yw
+zgL!IF60pMfQRkwZjPjYEk%wzZ^3xZK3Q^@$Emwcl&{&RZg@DVEDLYS0N>ZM5$#ndk
+z&22o*u=b&^trc3UMGkzzrL*~$;t?gd{w8WCC+z$)6{fY`v4CL%;?|JZtR3}&oLz8*
+zT?G#HsX)xAYvWho@h=pJpzsjcWp0%LD4s08onG)Nb4)MY=8K^XfVvcKVvP||0{idF
+zr>Wx=dX&);ID@-|u5Y#BAa0c8rW_t)Xfo<vlc#AAL?V;xK!(VoUOQfwYKLZS&i<-9
+z;;vFoDu&qH04x=`k?XASkyK@GT+_OrFRio<oEjgbKi&<{<pem1Bh|_{rd}3b6M_~k
+z#ug2gX33LeGUF6ujY8^-+NBC3N3Fo!SH?DlQ(x%FqF&l0I?|LVAq9|-_ZAzs`MtE0
+zz6|*#9u5wvDGZ0bong!tha)Mz6I;9$ZWQF+WC+Z02hfO#<HL~fql9E=*Ih)@0t$FV
+z%W2<l>4c@By|jKCCPsr7DjJ6t;eTIrmF;CpM`~(ysWB=S@seY-cC;IYp7eGp3%$l}
+z)oc?3j<N+0i5LM!Lg2p)9=a<RaaY_xwy2ck^!-q?ggIYnFfpFqE!+Gptg``+3UZ&B
+z|G7`opGD})?f*>DrN<0qs>+yfj#><OZl7$l^xn+cudeRjEGbCoGdi+?_*DlwButhM
+z2c4jyp#csao|(;WM}d0ov1}Oihu(2H(;^$M+d1l4WRg4(PiAdTKH$e65RXehqGcYp
+ziS@I=L*)Cyiu;g}I&7*~m`bn>o^%eHp8`K^wUK{qUM_Xl#K;;VHK+>&$DqLQV1~<L
+zJDOoVC28%&(mauG>BoxLuBrt&0}DAhEKn_^ER<H!yx{%QhA9tM0kEdHkOyS`=c%lV
+zq{%S;|CVPO-A_h<n$FyCVaW`RarknRGNlHhU*Qp*V8JL9k4MsRCTKGmV-lT;?XB>`
+zz-29QNvC|8F%an87xNYKcn*LCu89T8nVkc&?~&O83)5GbY)slt*#=)i7s;A<N)hyx
+zwh^cOb?iKU)IbRP=ka+qf+JT_=N|faOQrq_JH^K1`TFgfF^F{DYfaT@vyY6ISleTm
+zGL!<vL!F>_C=2r7N7+fk`X1KngTDCyUEafq@X5m_z1=DeiD@Q38P{+Ou8AdwgrjC5
+zajlbj!7Ae^jZ~9GGnmvF%|dV*Siz7~1$lG}zFHP5%BV8TD09lQN!w79WRZ;`=PM(z
+z0;YT`0PcRb5SM~SQ_OKjwTc~?W_G_IPe||U$;Um2U%fe+7X>%Nvy!xcXUbbT1miw0
+z=$X7_W&m0ay!h~`ae>C68mu@al*ia7R0saqO=sn$tE@ww372nWLhU^>%{WE>Eoln8
+zaeH(5Zly+xlW1Z@B{Z2HqS52V*oh`BC}k&quf19RS}N6$l#0qGWzl9DQkZ@85<PA+
+zldE}FJS4PxXhbzMsRmrwaRz~V3QLN=WqdEEvulNbgjbr&&1P5w4PCwA3{?jrWGCM~
+zX0DmWj<kYm`dP~kIl-=0KWu`Jc;838I{cbhtw0szBJu5{7M^n&!<`QEsTb#X9`$lT
+z57Hwj9Ps0kuw}6a#aAGdM8Uyjl-gC)G1hP^b}DQCDLs;KR8(o5(@&tS<A5C%$Hu%&
+zajcvfB#m7XUTidGKu@9pZHG2y*-%G$Ih9jXO6!k#h<t!VB52|u)nv~y!>(#UMH4E)
+z!&hPrOmR$HRF*}2C{e3A#U3h9d)gN68^|>O9=TO4Ga~u#5kl0}_*QP9IxEl~Ce;Vj
+zS3zvyQ+p-TKYiV8z>J$akDBH=i$W7}&)8|aN%_17$7$H|;eKWRKgAtrMwoyE;#kJp
+z>iJ{R+d4p$2q2;Y5EBQ7>@E&mk*MzVW>!EDsQ9Pd1Icl|=0d^U2HU!hP6MLe0bwp2
+zA=U!|OQM?{{^8dU?o^&w|I~Y5fw~zw)IT&*mzBRUy1Ljo^-=Z`fvN|N_J<BBAdS~k
+z!ALu4Q%-z*R{oO&4hJxm{nHfwfAoO3sOznOQIr1~0QZdfH=zHAn6N<6r7+IV)Vf=N
+z-qCbD?!=wp{X>gxG~k*Hc%03VftQZkoi*AD{-11-bt2%}_=-R;7ZY`jOzsFyAEWb!
+zVJNLPL#@4|8iv-c@m4Lu!^Uc7?VOsDWty>@T6^QN67|~9P?w&boWVpR2)d)gI@s*$
+zT0uPct)H#x^_Y(_q2El&g2<(pF8niAzCde(;c)XAp3awn@Z)3{qMO$l1?#O_cXL+a
+zB+yS96Q;w{xIBw9%-h2xp$%a(D0`Noi$$31BbukCM_lu$4sG_+rWsH9U`eD0eY3t3
+z@`vkyB5OW$_NhyNPE(&_JPvYO1XVd%SiaJPVza|ZguGogD*p`OzJ!Odk4wR7o=G7;
+zQFEN*_9WQcO`Vliy5G@VCnZ;Qb~fJ44e1$o^Tw=L_lA;Z-8Dw0CC}X_m5Q_J*xP61
+z2tVQGAnU9PA@k;{9QL{c=-~c_joC`W*8qxTI)7}foE-)SU;g6SD;S1P5oGCta0DrC
+zGXz?khB$Fn{Ycwuk%t&RTyJ!Mz8mnC0U+AYu}PkaA-t-gE*25%;RVKNKyWz!scpu6
+zZDKFBX5S4#lCQK!Ip%UxMsP%cC4T!8d`;mo#M{(B)h;Ilk3UVA`-O^+JuQDuUnt-K
+z=jEH2NuzvVs7mGT0rJ;Nz54;;pVk-{O`o<8h5~yAG9cx)%sJ+#d0-B8j!9{+{>1@9
+zYiz-m^g@6wE8^*umZD0JhIN!|&Ok-?2XhJ@B|oI&FfS^$rs90JhlZBoJW`e5b9j^-
+zWO>uD9oB-o4QKEBn$akVeT1MeUX-s%#m~lP<b&9IWGgbi$*OIzpK_3n^}!I)WVooa
+z#6_PWZ8QA$W_OwNhR51iZ{AQ8gp5IC_qMYoKTO<wW7Lxu951+0Q*<Z_wU3?^MX!Qs
+zKFIi5(!_vxR?8&ce&YV47mZ8#83_LZ{o>XZR!_h7SU~%Y_rx{QlrO<RbvccdJya54
+zEx3&41--x^p6UqQ4-K0v<iu-hD(5Xrz}`FaganW<;qBVLd$lhu5Wn*9nE3U4nha#d
+zqjK-c{nPa2P{JRx8UPnyz`s1eOA2Taz`!T`m}fLvc<|5nl(qw*BvH~+UyPRhKzyrh
+zxOKlLj5wBR=EU`4w)nCr{hTY!q<lpX5(s|w3QJ3;mfaaus{2>`$o+{oUb!PIS+x5N
+z+{O+YLa6?IE1#&A?RMZ&J}!O!vj>Os^y>J_BMi^Cu8;>FP)!5eagStg`4k8`f<9)s
+zLv>uniXJHc5tD}2a*xO+UycHT8lGykAS#<PAVxm$cbPxfCa|;$;o~~iE+XVlgHCw7
+zC#7nl^r~{M*TsmN95D{cIk!IhJ_X-wC?{Y<`4dUpmRY}yVmTSPk&p-vIF$+M=~`b#
+z6Xw_$9|qJhncu-46MlAh5ITV>tq7H&?$Q|yXO#aH{77;M;}%#Rn*u_i#Q#=kFoCjB
+zxM)O)sW@_wx=K{lJ|iyESH0iv9Nr111eP3eEA!SenTb%U12{RS*7qj0=;%^Kd#QiJ
+ziYTEU=jFY{zWsSqmqmw<7L@5T1o7NxWhht`9gu$(b|QZnjVAE)D;lyC=><hR)|0rK
+z4G=-Wk9u*;^2!F@ZPDmuT=Fj`zK22q4P|a@naT2k6OIr&5bt7mM+Zi{1dh<!#Q+MV
+zNV$%grklh|8>~hv=8piE3T9#-QVKCSaq-q&xr*zuRbfKtru+;Kkp5Si5+<6{tz}rp
+zigZWmiiYYR#xdxCbhhJz=wN$k9zPcR8H;AJErv2><3*Bm51h&CEJlpT9yo<pH&s}f
+zXzYb2r<w#K%?wiq#0>5`<Zq9U!x1gwEv^qA_gUtKoaUH|F#B+-I$;m;h@imuPKwTl
+zn58Fdpf)L2vT#)zU+}?#P)g7lir`yLqN5l4uSh3fGa;S@>1`w{pnaAJ%0k=ISmg0E
+zo$J6^H1-w0!^WV5w|yx36dtal`WN}DGpD-gqYjDTfjIaLtR}xxCDSo6v=}KHRM^9@
+z&T;nw5x5ee(K3%Z3QQF%sMId_cIRpr&3g$f><9ZoX7X_c7g4f{y)mf(?;`TLI@jLv
+z?N)ryzDJ)LsBZU+VnRH0X1E}KJ!}%#n_-<YL8nvAJG1pi@pQ)DNORI)b_#$>hEY9w
+z`8(=7Fd9^wGY;{_ggJK@ZR?yW!1!^^d;F^x%}=DG(7K8XMm$L~K*Np|t>vZmA5%Y|
+zINrWxnZFq_J7&ksTGEluekfNRCX$8u^xk+?w8Q1iII^7LA8Wc=uh=>E34C14fN(+~
+zjb&LKSzG|ur8^cG=n*d|U)DK;5`-D7c>o{;1qb8{cYdL5^ll*Y29ag^ZWs(}{Dq?&
+z7Vt6fu%BVSoqvD;RYW!I!KS^e-kCz_2@FvAByt<`2mpv<fkZr|^`JQiBI_`lhGk(!
+z_N*Sb+Ln<Xn})Qgi4eM-7qPD_UOjLAm9k+61#g=VRLjj+Bq9RB{*-?aNH;_9hvInV
+z1PSqXdP+6AuCc1VrYiH#W?-)Rn#1F?YJ)<4bv{8UexqS@(f!Bn_=kD5>xlE{aWp)%
+z7->KZs4&!M+Z9|_;(Qr<M|^-9J`VLlA0T%cdqRyI>bPRGNC2zLU&;bq*v@zaDlNR7
+zR!OB(0w7?XvMI3w1tc_A&fY$=RO&K>9q)K{?KeL9#X2nl`k!ouFF)XFC@Tui*%L4~
+zwNvTu3}=K5TH;uDS!^k3d+!l_hx$f?(hkYU(6NBYx@mz*Y6dZ7D@JF^5^p{aiT5zv
+z;Xjc--#|sw407DGZz<4^FBXBq5F)zwTQ|65$~FTfyft2wOiY&QG(ydKoz#wa?YKny
+z)9C@EX0c#XN}}K5dNFdMNo^+Os>0sS^c;E5Ky4zm)q;>J{J+z3sdUj)7tN@@gZSf7
+zJ|wiD$oI`e{Xe-gDV9P_(x}i7AaPVJn&m~NMi(84-RGbXy6@{lY?h66ze7!6Ee=i!
+zInre-6PCHrI9+8v4+)Zge*esLVEy0*)t)o|)801Zf98hgQ=EZH2bpZ=)5NN_2yjw#
+zP8Ewr(5WN{8DJpt*e!|G(gvZ5Pxywag$Agdns%%4+I<chK&;6@52mh48>H>|FMw9b
+zKb<-v)*Cb*Ao~hb;B*`Ee&trZYBi`{$ru%gmKbuXcPNb3lD3H3Jimki7;BEFp{bxX
+zFJ7Rk<~$d5(AGs1%w=$DDrj&3=?C4wX`U{m8^^=Z8R3YTB_A>ZA<nn`Er>OkmldWl
+zwo0ZyTNCB`dfUZA+chm*()HWtA2!JQ3>g${<ZEUqmU&IH*$?ZzOs2IhHpYEix|NPQ
+zJ-Pi715VXGJVDjN><!w*tid~hXaqgXjMCinP(wu6AN~G*C5MrlRjoO?J1hQ>8%Vr%
+zasf==&095e)fG}M%iIsk{PaQ>2|D59ppz^2pExvb9Ou9EI^`kN!0aXr*u3p0ex0b4
+z=AnHH#@v>`#o*LjN-yB0^^l)H2Nm=yD3|>1aNigv$f`s680kxF8B%d>SUG)YF0R~W
+z$TI5rvll2~&q4RSwu3})*@1!~z4l}@NsY#MwV(2<h@*@k1>Y=hbLZh-ce*Eq3<#rZ
+zxra}au9h@`-JaCDeW|)St?N40z`g~4rjZ?xu=?#W;cJyHNPXCV2DuxD%N1A2hAlFH
+zwTJm(6XPn#dA&{dq>&yd{5Lp=pa<%$*em=~TdQ%rn_v#5`><qe0k3yPzhk;_7^Ch6
+z``4jh8^vb#=_?9Hh_)q6T)5{?KdaF@G)h>I!IS>M^uNpl#N|wC@HMBcRTMT#SL;d7
+z<(&BuA6dLkkx|8fWw@PXzCeCBgDx@HJs@)L+j8y~gZ<df6K`wk{>)7)${p-|O7{G?
+z&|M6FI|A*^d_U+Of-3`+w(c~-YsQby|NH)g|G7xv|Nek^|Jex)g~z+)I0xPC0460S
+LFIp>X81%mY^Bg|U
+
+literal 0
+HcmV?d00001
+
diff --git a/patches/server/0487-fix-converting-txt-to-json-file.patch b/patches/server/0487-fix-converting-txt-to-json-file.patch
new file mode 100644
index 0000000000..7f63558c05
--- /dev/null
+++ b/patches/server/0487-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 9a3e73a5c206b78dfcf6f41a47b614342e52acc8..9d05e998d6df1069c2de69478a1f9688ac435e67 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -213,6 +213,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
+@@ -267,9 +273,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 061bba184c8bc2569ce1d413435ec1360fa14007..e80e6e402ae286358b7f26073329b2e10604153b 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -179,6 +179,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/0488-Add-worldborder-events.patch b/patches/server/0488-Add-worldborder-events.patch
new file mode 100644
index 0000000000..014dba4c88
--- /dev/null
+++ b/patches/server/0488-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/0489-Add-PlayerNameEntityEvent.patch b/patches/server/0489-Add-PlayerNameEntityEvent.patch
new file mode 100644
index 0000000000..8d982f76db
--- /dev/null
+++ b/patches/server/0489-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 244663ff31bdb29bbff8b3ffd9fe99279b173296..df9cdcb9544a171a5a07c65ba0150933fb70d5fc 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() && entity.canBeNameTagged()) {
+ 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/0490-Add-recipe-to-cook-events.patch b/patches/server/0490-Add-recipe-to-cook-events.patch
new file mode 100644
index 0000000000..416e598dc6
--- /dev/null
+++ b/patches/server/0490-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 b2c0de191fdc84d4a007373309a5df81cacd5466..4acf487f9a5f3fa828ee76f9708d6a2ae28707e1 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
+@@ -327,7 +327,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/0491-Add-Block-isValidTool.patch b/patches/server/0491-Add-Block-isValidTool.patch
new file mode 100644
index 0000000000..e0c42e8e37
--- /dev/null
+++ b/patches/server/0491-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/0492-Allow-using-signs-inside-spawn-protection.patch b/patches/server/0492-Allow-using-signs-inside-spawn-protection.patch
new file mode 100644
index 0000000000..3953df000d
--- /dev/null
+++ b/patches/server/0492-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 3d4ab22077eb358168b2959c3d1beb10b7104ab7..e23b1f98c4ea066e1c35e15454ed524a20f65b4d 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -1827,7 +1827,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/0493-Expand-world-key-API.patch b/patches/server/0493-Expand-world-key-API.patch
new file mode 100644
index 0000000000..7e7d90c838
--- /dev/null
+++ b/patches/server/0493-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 15da29058f80a2d7cf2be26c48421c1746815a10..a070b2a83edaa702b13bc6d3026914126c211576 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java
+@@ -521,5 +521,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 5ae8f646083fb580ac8d28fcbfe8ed2208b45d09..3cfacacd1d8fd17ec4b54936afd8124823b1a00b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -1185,9 +1185,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);
+@@ -1313,7 +1319,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
+@@ -1409,6 +1415,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 5f0fd1d4fe419435ac7293106c0ebb5382d64074..8d9380b50424546bcebdfa868635f94c8efa5899 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+@@ -538,6 +538,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
+
+ /**
diff --git a/patches/server/0494-Add-fast-alternative-constructor-for-Rotations.patch b/patches/server/0494-Add-fast-alternative-constructor-for-Rotations.patch
new file mode 100644
index 0000000000..e28775fc9a
--- /dev/null
+++ b/patches/server/0494-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/0495-Drop-carried-item-when-player-has-disconnected.patch b/patches/server/0495-Drop-carried-item-when-player-has-disconnected.patch
new file mode 100644
index 0000000000..42055a9f10
--- /dev/null
+++ b/patches/server/0495-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 e80e6e402ae286358b7f26073329b2e10604153b..6ddcc928a49630e3a0f7f40cca496642419efa2c 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -537,6 +537,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/0496-forced-whitelist-use-configurable-kick-message.patch b/patches/server/0496-forced-whitelist-use-configurable-kick-message.patch
new file mode 100644
index 0000000000..f9aab54d8f
--- /dev/null
+++ b/patches/server/0496-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 b28a2756429270414f8ef89539f91ce0595d13c6..0ab92fb362729108beca6bc0230fb6fb06ca6280 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -2325,7 +2325,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/0497-Don-t-ignore-result-of-PlayerEditBookEvent.patch b/patches/server/0497-Don-t-ignore-result-of-PlayerEditBookEvent.patch
new file mode 100644
index 0000000000..890d12fe22
--- /dev/null
+++ b/patches/server/0497-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 e23b1f98c4ea066e1c35e15454ed524a20f65b4d..ddf2b9c6de079f55c3ac8836345c1e681f23fcac 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -1219,7 +1219,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/0498-Expose-protocol-version.patch b/patches/server/0498-Expose-protocol-version.patch
new file mode 100644
index 0000000000..cd6a7ab2b0
--- /dev/null
+++ b/patches/server/0498-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 8d9380b50424546bcebdfa868635f94c8efa5899..30f2ddb66385718304b4b0302f7efcafd54bc42a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+@@ -543,6 +543,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
+
+ /**
diff --git a/patches/server/0499-Enhance-console-tab-completions-for-brigadier-comman.patch b/patches/server/0499-Enhance-console-tab-completions-for-brigadier-comman.patch
new file mode 100644
index 0000000000..3d1a0fb945
--- /dev/null
+++ b/patches/server/0499-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 9d05e998d6df1069c2de69478a1f9688ac435e67..7c92b2f0a59fe222ad13a998476e312bf571a1bf 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -187,7 +187,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\"");
+@@ -220,6 +220,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/0500-Fix-PlayerItemConsumeEvent-cancelling-properly.patch b/patches/server/0500-Fix-PlayerItemConsumeEvent-cancelling-properly.patch
new file mode 100644
index 0000000000..7ab0446f86
--- /dev/null
+++ b/patches/server/0500-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 2d3f418b5a7a5414ca5be8525146e480add51635..509139ab9a3070c8c96d14fdc4d07bc229b41bd6 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -4149,6 +4149,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ }
+ entityPlayer.getBukkitEntity().updateInventory();
+ entityPlayer.getBukkitEntity().updateScaledHealth();
++ this.stopUsingItem(); // Paper - event is using an item, clear active item to reset its use
+ return;
+ }
+
diff --git a/patches/server/0501-Add-bypass-host-check.patch b/patches/server/0501-Add-bypass-host-check.patch
new file mode 100644
index 0000000000..53fea6e14d
--- /dev/null
+++ b/patches/server/0501-Add-bypass-host-check.patch
@@ -0,0 +1,30 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Shane Freeder <[email protected]>
+Date: Sun, 18 Apr 2021 21:27:01 +0100
+Subject: [PATCH] Add bypass host check
+
+Paper.bypassHostCheck
+
+Seriously, fix your firewalls. -.-
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
+index 0c1bdf2329936ce479a2cc53e8a46bd2ad685ec1..a5bbea6a073e00c10c3c5facd997eb8473fd9a5f 100644
+--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
+@@ -32,6 +32,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL
+ private static final Component IGNORE_STATUS_REASON = Component.translatable("disconnect.ignoring_status_request");
+ private final MinecraftServer server;
+ private final Connection connection;
++ private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper
+
+ public ServerHandshakePacketListenerImpl(MinecraftServer server, Connection connection) {
+ this.server = server;
+@@ -164,7 +165,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL
+ String[] split = packet.hostName().split("\00");
+ if (!handledByEvent && proxyLogicEnabled) { // Paper
+ // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above!
+- if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) {
++ if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper - Add bypass host check
+ this.connection.hostname = split[0];
+ this.connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getPort());
+ this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] );
diff --git a/patches/server/0502-Set-area-affect-cloud-rotation.patch b/patches/server/0502-Set-area-affect-cloud-rotation.patch
new file mode 100644
index 0000000000..92bb3dc84a
--- /dev/null
+++ b/patches/server/0502-Set-area-affect-cloud-rotation.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Mon, 5 Apr 2021 16:58:20 -0400
+Subject: [PATCH] Set area affect cloud rotation
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
+index 0ebcf7ed1cb9e896de5fbac60afdb937ba86d15c..95c2ef53e6c5574e69614d8058eb3ed94ab6b0d6 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
+@@ -426,7 +426,7 @@ public final class CraftEntityTypes {
+ register(new EntityTypeData<>(EntityType.EXPERIENCE_ORB, ExperienceOrb.class, CraftExperienceOrb::new,
+ spawnData -> new net.minecraft.world.entity.ExperienceOrb(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), 0, org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM, null, null) // Paper
+ ));
+- register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, spawnData -> new net.minecraft.world.entity.AreaEffectCloud(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z())));
++ register(new EntityTypeData<>(EntityType.AREA_EFFECT_CLOUD, AreaEffectCloud.class, CraftAreaEffectCloud::new, createAndMove(net.minecraft.world.entity.EntityType.AREA_EFFECT_CLOUD))); // Paper - set area effect cloud rotation
+ register(new EntityTypeData<>(EntityType.EGG, Egg.class, CraftEgg::new, spawnData -> new ThrownEgg(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), new net.minecraft.world.item.ItemStack(Items.EGG))));
+ register(new EntityTypeData<>(EntityType.LEASH_KNOT, LeashHitch.class, CraftLeash::new, spawnData -> new LeashFenceKnotEntity(spawnData.minecraftWorld(), BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z())))); // SPIGOT-5732: LeashHitch has no direction and is always centered at a block
+ register(new EntityTypeData<>(EntityType.SNOWBALL, Snowball.class, CraftSnowball::new, spawnData -> new net.minecraft.world.entity.projectile.Snowball(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), new net.minecraft.world.item.ItemStack(Items.SNOWBALL))));
diff --git a/patches/server/0503-add-isDeeplySleeping-to-HumanEntity.patch b/patches/server/0503-add-isDeeplySleeping-to-HumanEntity.patch
new file mode 100644
index 0000000000..645f1201ea
--- /dev/null
+++ b/patches/server/0503-add-isDeeplySleeping-to-HumanEntity.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 8 Apr 2021 17:36:10 -0700
+Subject: [PATCH] add isDeeplySleeping to HumanEntity
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
+index 42c41cd4b280839a35f77e04d0dc6a06d262d8f3..d5c4184e0d93eabc699661829ff4bf9b46d27142 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java
+@@ -132,6 +132,13 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity {
+ }
+ // Paper end
+
++ // Paper start
++ @Override
++ public boolean isDeeplySleeping() {
++ return getHandle().isSleepingLongEnough();
++ }
++ // Paper end
++
+ @Override
+ public int getSleepTicks() {
+ return this.getHandle().sleepCounter;
diff --git a/patches/server/0504-add-consumeFuel-to-FurnaceBurnEvent.patch b/patches/server/0504-add-consumeFuel-to-FurnaceBurnEvent.patch
new file mode 100644
index 0000000000..150b4fe7c0
--- /dev/null
+++ b/patches/server/0504-add-consumeFuel-to-FurnaceBurnEvent.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 22 Apr 2021 16:45:28 -0700
+Subject: [PATCH] add consumeFuel to FurnaceBurnEvent
+
+
+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 4acf487f9a5f3fa828ee76f9708d6a2ae28707e1..7b63e2fd004ae452c7350f0b2d8d7c57a42891ea 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
+@@ -250,7 +250,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit
+ if (blockEntity.isLit() && furnaceBurnEvent.isBurning()) {
+ // CraftBukkit end
+ flag1 = true;
+- if (flag3) {
++ if (flag3 && furnaceBurnEvent.willConsumeFuel()) { // Paper - add consumeFuel to FurnaceBurnEvent
+ Item item = itemstack.getItem();
+
+ itemstack.shrink(1);
diff --git a/patches/server/0505-add-get-set-drop-chance-to-EntityEquipment.patch b/patches/server/0505-add-get-set-drop-chance-to-EntityEquipment.patch
new file mode 100644
index 0000000000..e794470410
--- /dev/null
+++ b/patches/server/0505-add-get-set-drop-chance-to-EntityEquipment.patch
@@ -0,0 +1,72 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 22 Apr 2021 00:28:11 -0700
+Subject: [PATCH] add get-set drop chance to EntityEquipment
+
+== AT ==
+public net.minecraft.world.entity.Mob getEquipmentDropChance(Lnet/minecraft/world/entity/EquipmentSlot;)F
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java
+index cb704cef3845727c465fe3ea7210a11545da56c8..fdcc414f4fa246082ad0732133c870d915ae3084 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java
+@@ -244,15 +244,22 @@ public class CraftEntityEquipment implements EntityEquipment {
+ public void setBootsDropChance(float chance) {
+ this.setDropChance(net.minecraft.world.entity.EquipmentSlot.FEET, chance);
+ }
++ // Paper start
++ @Override
++ public float getDropChance(EquipmentSlot slot) {
++ return getDropChance(CraftEquipmentSlot.getNMS(slot));
++ }
++
++ @Override
++ public void setDropChance(EquipmentSlot slot, float chance) {
++ setDropChance(CraftEquipmentSlot.getNMS(slot), chance);
++ }
++ // Paper end
+
+ private void setDropChance(net.minecraft.world.entity.EquipmentSlot slot, float chance) {
+ Preconditions.checkArgument(this.entity.getHandle() instanceof Mob, "Cannot set drop chance for non-Mob entity");
+
+- if (slot == net.minecraft.world.entity.EquipmentSlot.MAINHAND || slot == net.minecraft.world.entity.EquipmentSlot.OFFHAND) {
+- ((Mob) this.entity.getHandle()).handDropChances[slot.getIndex()] = chance;
+- } else {
+- ((Mob) this.entity.getHandle()).armorDropChances[slot.getIndex()] = chance;
+- }
++ ((Mob) this.entity.getHandle()).setDropChance(slot, chance); // Paper - use setter on Mob
+ }
+
+ private float getDropChance(net.minecraft.world.entity.EquipmentSlot slot) {
+@@ -260,10 +267,6 @@ public class CraftEntityEquipment implements EntityEquipment {
+ return 1;
+ }
+
+- if (slot == net.minecraft.world.entity.EquipmentSlot.MAINHAND || slot == net.minecraft.world.entity.EquipmentSlot.OFFHAND) {
+- return ((Mob) this.entity.getHandle()).handDropChances[slot.getIndex()];
+- } else {
+- return ((Mob) this.entity.getHandle()).armorDropChances[slot.getIndex()];
+- }
++ return ((Mob) this.entity.getHandle()).getEquipmentDropChance(slot); // Paper - use getter on Mob
+ }
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java
+index f039c5041e783ef55e19194cb7db7610429b1d96..107fa1d4bd977d17dc062da280dda46eb3c5f81c 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java
+@@ -353,4 +353,15 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i
+ public void setBootsDropChance(float chance) {
+ throw new UnsupportedOperationException("Cannot set drop chance for PlayerInventory");
+ }
++ // Paper start
++ @Override
++ public float getDropChance(EquipmentSlot slot) {
++ return 1;
++ }
++
++ @Override
++ public void setDropChance(EquipmentSlot slot, float chance) {
++ throw new UnsupportedOperationException("Cannot set drop chance for PlayerInventory");
++ }
++ // Paper end
+ }
diff --git a/patches/server/0506-fix-PigZombieAngerEvent-cancellation.patch b/patches/server/0506-fix-PigZombieAngerEvent-cancellation.patch
new file mode 100644
index 0000000000..040d076f37
--- /dev/null
+++ b/patches/server/0506-fix-PigZombieAngerEvent-cancellation.patch
@@ -0,0 +1,35 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Trigary <[email protected]>
+Date: Thu, 18 Mar 2021 21:38:01 +0100
+Subject: [PATCH] fix PigZombieAngerEvent cancellation
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
+index faaaee79e14adb62e810641ccc8f51428d39883d..03e3cbe73119ca76417d4dd192e1560bdfc373ec 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
++++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java
+@@ -56,6 +56,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
+ private static final int ALERT_RANGE_Y = 10;
+ private static final UniformInt ALERT_INTERVAL = TimeUtil.rangeOfSeconds(4, 6);
+ private int ticksUntilNextAlert;
++ private HurtByTargetGoal pathfinderGoalHurtByTarget; // Paper - fix PigZombieAngerEvent cancellation
+
+ public ZombifiedPiglin(EntityType<? extends ZombifiedPiglin> type, Level world) {
+ super(type, world);
+@@ -71,7 +72,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
+ protected void addBehaviourGoals() {
+ this.goalSelector.addGoal(2, new ZombieAttackGoal(this, 1.0D, false));
+ this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0D));
+- this.targetSelector.addGoal(1, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers());
++ this.targetSelector.addGoal(1, pathfinderGoalHurtByTarget = (new HurtByTargetGoal(this, new Class[0])).setAlertOthers()); // Paper - fix PigZombieAngerEvent cancellation
+ this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 10, true, false, this::isAngryAt));
+ this.targetSelector.addGoal(3, new ResetUniversalAngerTargetGoal<>(this, true));
+ }
+@@ -179,6 +180,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob {
+ this.level().getCraftServer().getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
+ this.setPersistentAngerTarget(null);
++ pathfinderGoalHurtByTarget.stop(); // Paper - fix PigZombieAngerEvent cancellation
+ return;
+ }
+ this.setRemainingPersistentAngerTime(event.getNewAnger());
diff --git a/patches/server/0507-fix-PlayerItemHeldEvent-firing-twice.patch b/patches/server/0507-fix-PlayerItemHeldEvent-firing-twice.patch
new file mode 100644
index 0000000000..631dbbe515
--- /dev/null
+++ b/patches/server/0507-fix-PlayerItemHeldEvent-firing-twice.patch
@@ -0,0 +1,18 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: chickeneer <[email protected]>
+Date: Thu, 22 Apr 2021 19:02:07 -0700
+Subject: [PATCH] fix PlayerItemHeldEvent firing twice
+
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index ddf2b9c6de079f55c3ac8836345c1e681f23fcac..47f925707d8756796ce169b7cbd8f2410b0b7104 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -2014,6 +2014,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+ if (this.player.isImmobile()) return; // CraftBukkit
+ if (packet.getSlot() >= 0 && packet.getSlot() < Inventory.getSelectionSize()) {
++ if (packet.getSlot() == this.player.getInventory().selected) { return; } // Paper - don't fire itemheldevent when there wasn't a slot change
+ PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getCraftPlayer(), this.player.getInventory().selected, packet.getSlot());
+ this.cserver.getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
diff --git a/patches/server/0508-Add-PlayerDeepSleepEvent.patch b/patches/server/0508-Add-PlayerDeepSleepEvent.patch
new file mode 100644
index 0000000000..1750b3bc37
--- /dev/null
+++ b/patches/server/0508-Add-PlayerDeepSleepEvent.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 21 Apr 2021 15:58:19 -0700
+Subject: [PATCH] Add PlayerDeepSleepEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
+index 136a6b8e2065c7bada396e0f54f91c841a85c3d8..13f21822d0eb1d167b9b71addb46c4f508301c8d 100644
+--- a/src/main/java/net/minecraft/world/entity/player/Player.java
++++ b/src/main/java/net/minecraft/world/entity/player/Player.java
+@@ -265,6 +265,13 @@ public abstract class Player extends LivingEntity {
+
+ if (this.isSleeping()) {
+ ++this.sleepCounter;
++ // Paper start - Add PlayerDeepSleepEvent
++ if (this.sleepCounter == SLEEP_DURATION) {
++ if (!new io.papermc.paper.event.player.PlayerDeepSleepEvent((org.bukkit.entity.Player) getBukkitEntity()).callEvent()) {
++ this.sleepCounter = Integer.MIN_VALUE;
++ }
++ }
++ // Paper end - Add PlayerDeepSleepEvent
+ if (this.sleepCounter > 100) {
+ this.sleepCounter = 100;
+ }
diff --git a/patches/server/0509-More-World-API.patch b/patches/server/0509-More-World-API.patch
new file mode 100644
index 0000000000..2b853eb7b0
--- /dev/null
+++ b/patches/server/0509-More-World-API.patch
@@ -0,0 +1,57 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Tue, 7 Jul 2020 10:52:34 -0700
+Subject: [PATCH] More World API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index 69464a4fe467128121fe1a9ba08c9c7154e22c7f..f20717ee4505d28fa44f0f9ced5c71ae75c8a6d9 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -2130,6 +2130,28 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+ return new CraftStructureSearchResult(CraftStructure.minecraftToBukkit(found.getSecond().value()), CraftLocation.toBukkit(found.getFirst(), this));
+ }
+
++ // Paper start
++ @Override
++ public double getCoordinateScale() {
++ return getHandle().dimensionType().coordinateScale();
++ }
++
++ @Override
++ public boolean isFixedTime() {
++ return getHandle().dimensionType().hasFixedTime();
++ }
++
++ @Override
++ public Collection<org.bukkit.Material> getInfiniburn() {
++ return com.google.common.collect.Sets.newHashSet(com.google.common.collect.Iterators.transform(net.minecraft.core.registries.BuiltInRegistries.BLOCK.getTagOrEmpty(this.getHandle().dimensionType().infiniburn()).iterator(), blockHolder -> CraftBlockType.minecraftToBukkit(blockHolder.value())));
++ }
++
++ @Override
++ public void sendGameEvent(Entity sourceEntity, org.bukkit.GameEvent gameEvent, Vector position) {
++ getHandle().gameEvent(sourceEntity != null ? ((CraftEntity) sourceEntity).getHandle(): null, net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(gameEvent.getKey())).orElseThrow(), org.bukkit.craftbukkit.util.CraftVector.toBlockPos(position));
++ }
++ // Paper end
++
+ @Override
+ public BiomeSearchResult locateNearestBiome(Location origin, int radius, Biome... biomes) {
+ return this.locateNearestBiome(origin, radius, 32, 64, biomes);
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java
+index 3071ac1ac0e733d73dade49597a39f7d156bbc04..967445b2eb158454100a27369a1f463d69f54f27 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftVector.java
+@@ -12,4 +12,13 @@ public final class CraftVector {
+ public static net.minecraft.world.phys.Vec3 toNMS(org.bukkit.util.Vector bukkit) {
+ return new net.minecraft.world.phys.Vec3(bukkit.getX(), bukkit.getY(), bukkit.getZ());
+ }
++ // Paper start
++ public static org.bukkit.util.Vector toBukkit(net.minecraft.core.BlockPos blockPosition) {
++ return new org.bukkit.util.Vector(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
++ }
++
++ public static net.minecraft.core.BlockPos toBlockPos(org.bukkit.util.Vector bukkit) {
++ return net.minecraft.core.BlockPos.containing(bukkit.getX(), bukkit.getY(), bukkit.getZ());
++ }
++ // Paper end
+ }
diff --git a/patches/server/0510-Add-PlayerBedFailEnterEvent.patch b/patches/server/0510-Add-PlayerBedFailEnterEvent.patch
new file mode 100644
index 0000000000..375fec28c9
--- /dev/null
+++ b/patches/server/0510-Add-PlayerBedFailEnterEvent.patch
@@ -0,0 +1,36 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 24 Dec 2020 12:27:41 -0800
+Subject: [PATCH] Add PlayerBedFailEnterEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/BedBlock.java b/src/main/java/net/minecraft/world/level/block/BedBlock.java
+index 1e5f1b4c746fe9a105c2771c785976aae70a9a3e..30db6ae29e2fe03866dfa656330f7341e0a3f9ab 100644
+--- a/src/main/java/net/minecraft/world/level/block/BedBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/BedBlock.java
+@@ -120,14 +120,23 @@ public class BedBlock extends HorizontalDirectionalBlock implements EntityBlock
+ BlockPos finalblockposition = pos;
+ // CraftBukkit end
+ player.startSleepInBed(pos).ifLeft((entityhuman_enumbedresult) -> {
++ // Paper start - PlayerBedFailEnterEvent
++ if (entityhuman_enumbedresult != null) {
++ io.papermc.paper.event.player.PlayerBedFailEnterEvent event = new io.papermc.paper.event.player.PlayerBedFailEnterEvent((org.bukkit.entity.Player) player.getBukkitEntity(), io.papermc.paper.event.player.PlayerBedFailEnterEvent.FailReason.values()[entityhuman_enumbedresult.ordinal()], org.bukkit.craftbukkit.block.CraftBlock.at(world, finalblockposition), !world.dimensionType().bedWorks(), io.papermc.paper.adventure.PaperAdventure.asAdventure(entityhuman_enumbedresult.getMessage()));
++ if (!event.callEvent()) {
++ return;
++ }
++ // Paper end - PlayerBedFailEnterEvent
+ // CraftBukkit start - handling bed explosion from below here
+- if (!world.dimensionType().bedWorks()) {
++ if (event.getWillExplode()) { // Paper - PlayerBedFailEnterEvent
+ this.explodeBed(finaliblockdata, world, finalblockposition);
+ } else
+ // CraftBukkit end
+ if (entityhuman_enumbedresult.getMessage() != null) {
+- player.displayClientMessage(entityhuman_enumbedresult.getMessage(), true);
++ final net.kyori.adventure.text.Component message = event.getMessage(); // Paper - PlayerBedFailEnterEvent
++ if (message != null) player.displayClientMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(message), true); // Paper - PlayerBedFailEnterEvent
+ }
++ } // Paper - PlayerBedFailEnterEvent
+
+ });
+ return InteractionResult.SUCCESS_SERVER;
diff --git a/patches/server/0511-Implement-methods-to-convert-between-Component-and-B.patch b/patches/server/0511-Implement-methods-to-convert-between-Component-and-B.patch
new file mode 100644
index 0000000000..c5765e9de7
--- /dev/null
+++ b/patches/server/0511-Implement-methods-to-convert-between-Component-and-B.patch
@@ -0,0 +1,55 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Sat, 24 Apr 2021 02:09:32 -0700
+Subject: [PATCH] Implement methods to convert between Component and
+ Brigadier's Message
+
+
+diff --git a/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..dd6012b6a097575b2d1471be5069eccee4537c0a
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/brigadier/PaperBrigadierProviderImpl.java
+@@ -0,0 +1,30 @@
++package io.papermc.paper.brigadier;
++
++import com.mojang.brigadier.Message;
++import io.papermc.paper.adventure.PaperAdventure;
++import net.kyori.adventure.text.Component;
++import net.kyori.adventure.text.ComponentLike;
++import net.minecraft.network.chat.ComponentUtils;
++import org.checkerframework.checker.nullness.qual.NonNull;
++
++import static java.util.Objects.requireNonNull;
++
++public enum PaperBrigadierProviderImpl implements PaperBrigadierProvider {
++ INSTANCE;
++
++ PaperBrigadierProviderImpl() {
++ PaperBrigadierProvider.initialize(this);
++ }
++
++ @Override
++ public @NonNull Message message(final @NonNull ComponentLike componentLike) {
++ requireNonNull(componentLike, "componentLike");
++ return PaperAdventure.asVanilla(componentLike.asComponent());
++ }
++
++ @Override
++ public @NonNull Component componentFromMessage(final @NonNull Message message) {
++ requireNonNull(message, "message");
++ return PaperAdventure.asAdventure(ComponentUtils.fromMessage(message));
++ }
++}
+diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+index 7c92b2f0a59fe222ad13a998476e312bf571a1bf..761663514b98a1c0a0a905150411aff450a0cc50 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -224,6 +224,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ 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
++ io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // Paper - init PaperBrigadierProvider
+
+ this.setPvpAllowed(dedicatedserverproperties.pvp);
+ this.setFlightAllowed(dedicatedserverproperties.allowFlight);
diff --git a/patches/server/0512-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch b/patches/server/0512-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch
new file mode 100644
index 0000000000..017cc4e93e
--- /dev/null
+++ b/patches/server/0512-Expand-PlayerRespawnEvent-fix-passed-parameter-issue.patch
@@ -0,0 +1,26 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: HexedHero <[email protected]>
+Date: Fri, 23 Apr 2021 22:42:42 +0100
+Subject: [PATCH] Expand PlayerRespawnEvent, fix passed parameter issues
+
+Co-authored-by: Jake Potrebic <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index f046b8e6307079878d94eab615a5463a807e6c6c..c22dce8b30c8af3e37b497b0d97c6ceef0295883 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -1447,7 +1447,13 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ Player respawnPlayer = this.getBukkitEntity();
+ Location location = CraftLocation.toBukkit(teleportTransition.position(), teleportTransition.newLevel().getWorld(), teleportTransition.yRot(), teleportTransition.xRot());
+
+- PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn, isAnchorSpawn, reason);
++ // Paper start - respawn flags
++ com.google.common.collect.ImmutableSet.Builder<org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag> builder = com.google.common.collect.ImmutableSet.builder();
++ if (reason == org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.END_PORTAL) {
++ builder.add(org.bukkit.event.player.PlayerRespawnEvent.RespawnFlag.END_PORTAL);
++ }
++ PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(respawnPlayer, location, isBedSpawn, isAnchorSpawn, reason, builder);
++ // Paper end - respawn flags
+ this.level().getCraftServer().getPluginManager().callEvent(respawnEvent);
+ // Spigot Start
+ if (this.connection.isDisconnected()) {
diff --git a/patches/server/0513-Introduce-beacon-activation-deactivation-events.patch b/patches/server/0513-Introduce-beacon-activation-deactivation-events.patch
new file mode 100644
index 0000000000..336acbe811
--- /dev/null
+++ b/patches/server/0513-Introduce-beacon-activation-deactivation-events.patch
@@ -0,0 +1,37 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spyridon Pagkalos <[email protected]>
+Date: Thu, 25 Mar 2021 20:28:04 +0200
+Subject: [PATCH] Introduce beacon activation/deactivation events
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
+index dc3171b1493d7c4c8ddf1c79587c4e27bd819c17..8aab6f68f576fb022eb59798585e264f5aafbc69 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/BeaconBlockEntity.java
+@@ -225,6 +225,15 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
+ BeaconBlockEntity.playSound(world, pos, SoundEvents.BEACON_AMBIENT);
+ }
+ }
++ // Paper start - beacon activation/deactivation events
++ if (i1 <= 0 && blockEntity.levels > 0) {
++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
++ new io.papermc.paper.event.block.BeaconActivatedEvent(block).callEvent();
++ } else if (i1 > 0 && blockEntity.levels <= 0) {
++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(world, pos);
++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent();
++ }
++ // Paper end - beacon activation/deactivation events
+
+ if (blockEntity.lastCheckY >= l) {
+ blockEntity.lastCheckY = world.getMinY() - 1;
+@@ -282,6 +291,10 @@ public class BeaconBlockEntity extends BlockEntity implements MenuProvider, Name
+
+ @Override
+ public void setRemoved() {
++ // Paper start - beacon activation/deactivation events
++ org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(level, worldPosition);
++ new io.papermc.paper.event.block.BeaconDeactivatedEvent(block).callEvent();
++ // Paper end - beacon activation/deactivation events
+ BeaconBlockEntity.playSound(this.level, this.worldPosition, SoundEvents.BEACON_DEACTIVATE);
+ super.setRemoved();
+ }
diff --git a/patches/server/0514-Add-Channel-initialization-listeners.patch b/patches/server/0514-Add-Channel-initialization-listeners.patch
new file mode 100644
index 0000000000..430324c8f2
--- /dev/null
+++ b/patches/server/0514-Add-Channel-initialization-listeners.patch
@@ -0,0 +1,155 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Thu, 29 Apr 2021 21:19:33 +0200
+Subject: [PATCH] Add Channel initialization listeners
+
+
+diff --git a/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..88099df34c2d74daba9645aadf65b446ca795a91
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/network/ChannelInitializeListener.java
+@@ -0,0 +1,15 @@
++package io.papermc.paper.network;
++
++import io.netty.channel.Channel;
++import org.checkerframework.checker.nullness.qual.NonNull;
++
++/**
++ * Internal API to register channel initialization listeners.
++ * <p>
++ * This is not officially supported API and we make no guarantees to the existence or state of this interface.
++ */
++@FunctionalInterface
++public interface ChannelInitializeListener {
++
++ void afterInitChannel(@NonNull Channel channel);
++}
+diff --git a/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java b/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..30e62719e0a83525daa33cf41cb61df360c0e046
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/network/ChannelInitializeListenerHolder.java
+@@ -0,0 +1,74 @@
++package io.papermc.paper.network;
++
++import io.netty.channel.Channel;
++import net.kyori.adventure.key.Key;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++
++import java.util.Collections;
++import java.util.HashMap;
++import java.util.Map;
++
++/**
++ * Internal API to register channel initialization listeners.
++ * <p>
++ * This is not officially supported API and we make no guarantees to the existence or state of this class.
++ */
++public final class ChannelInitializeListenerHolder {
++
++ private static final Map<Key, ChannelInitializeListener> LISTENERS = new HashMap<>();
++ private static final Map<Key, ChannelInitializeListener> IMMUTABLE_VIEW = Collections.unmodifiableMap(LISTENERS);
++
++ private ChannelInitializeListenerHolder() {
++ }
++
++ /**
++ * Registers whether an initialization listener is registered under the given key.
++ *
++ * @param key key
++ * @return whether an initialization listener is registered under the given key
++ */
++ public static boolean hasListener(@NonNull Key key) {
++ return LISTENERS.containsKey(key);
++ }
++
++ /**
++ * Registers a channel initialization listener called after ServerConnection is initialized.
++ *
++ * @param key key
++ * @param listener initialization listeners
++ */
++ public static void addListener(@NonNull Key key, @NonNull ChannelInitializeListener listener) {
++ LISTENERS.put(key, listener);
++ }
++
++ /**
++ * Removes and returns an initialization listener registered by the given key if present.
++ *
++ * @param key key
++ * @return removed initialization listener if present
++ */
++ public static @Nullable ChannelInitializeListener removeListener(@NonNull Key key) {
++ return LISTENERS.remove(key);
++ }
++
++ /**
++ * Returns an immutable map of registered initialization listeners.
++ *
++ * @return immutable map of registered initialization listeners
++ */
++ public static @NonNull Map<Key, ChannelInitializeListener> getListeners() {
++ return IMMUTABLE_VIEW;
++ }
++
++ /**
++ * Calls the registered listeners with the given channel.
++ *
++ * @param channel channel
++ */
++ public static void callListeners(@NonNull Channel channel) {
++ for (ChannelInitializeListener listener : LISTENERS.values()) {
++ listener.afterInitChannel(channel);
++ }
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/network/ConnectionEvent.java b/src/main/java/io/papermc/paper/network/ConnectionEvent.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0d7e7db9e37ef0183c32b217bd944fb4f41ab83a
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/network/ConnectionEvent.java
+@@ -0,0 +1,10 @@
++package io.papermc.paper.network;
++
++/**
++ * Internal connection pipeline events.
++ */
++public enum ConnectionEvent {
++
++ COMPRESSION_THRESHOLD_SET,
++ COMPRESSION_DISABLED
++}
+diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
+index 448bde861b4190a678a8ccb63cade43c4a3d6ad9..f5fe87103ce53ba07b43c6c10fc9dafcc5ea4958 100644
+--- a/src/main/java/net/minecraft/network/Connection.java
++++ b/src/main/java/net/minecraft/network/Connection.java
+@@ -680,6 +680,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ } else {
+ this.channel.pipeline().addAfter("prepender", "compress", new CompressionEncoder(compressionThreshold));
+ }
++ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_THRESHOLD_SET); // Paper - Add Channel initialization listeners
+ } else {
+ if (this.channel.pipeline().get("decompress") instanceof CompressionDecoder) {
+ this.channel.pipeline().remove("decompress");
+@@ -688,6 +689,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ if (this.channel.pipeline().get("compress") instanceof CompressionEncoder) {
+ this.channel.pipeline().remove("compress");
+ }
++ this.channel.pipeline().fireUserEventTriggered(io.papermc.paper.network.ConnectionEvent.COMPRESSION_DISABLED); // Paper - Add Channel initialization listeners
+ }
+
+ }
+diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+index 64119f2188f8958b8a5913fec82ac5bba1b74b2a..d6d7f1c446ba5507f67038ff27775ba75156f4a7 100644
+--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+@@ -115,6 +115,7 @@ public class ServerConnectionListener {
+ pending.add(object); // Paper - prevent blocking on adding a new connection while the server is ticking
+ ((Connection) object).configurePacketHandler(channelpipeline);
+ ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object));
++ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners
+ }
+ }).group(eventloopgroup).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit
+ }
diff --git a/patches/server/0515-Send-empty-commands-if-tab-completion-is-disabled.patch b/patches/server/0515-Send-empty-commands-if-tab-completion-is-disabled.patch
new file mode 100644
index 0000000000..35cf408aa7
--- /dev/null
+++ b/patches/server/0515-Send-empty-commands-if-tab-completion-is-disabled.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Shane Freeder <[email protected]>
+Date: Mon, 26 Apr 2021 01:27:08 +0100
+Subject: [PATCH] Send empty commands if tab completion is disabled
+
+
+diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java
+index 2dcb1a1bdba1d1cad3caa117d85a8619ed9e67b4..d602c713696e23ba6a2d542b2e9e2cce46d79a66 100644
+--- a/src/main/java/net/minecraft/commands/Commands.java
++++ b/src/main/java/net/minecraft/commands/Commands.java
+@@ -453,7 +453,12 @@ public class Commands {
+ }
+
+ public void sendCommands(ServerPlayer player) {
+- if ( org.spigotmc.SpigotConfig.tabComplete < 0 ) return; // Spigot
++ // Paper start - Send empty commands if tab completion is disabled
++ if (org.spigotmc.SpigotConfig.tabComplete < 0) {
++ player.connection.send(new ClientboundCommandsPacket(new RootCommandNode<>()));
++ return;
++ }
++ // Paper end - Send empty commands if tab completion is disabled
+ // CraftBukkit start
+ // Register Vanilla commands into builtRoot as before
+ // Paper start - Perf: Async command map building
diff --git a/patches/server/0516-Add-more-WanderingTrader-API.patch b/patches/server/0516-Add-more-WanderingTrader-API.patch
new file mode 100644
index 0000000000..4221f28015
--- /dev/null
+++ b/patches/server/0516-Add-more-WanderingTrader-API.patch
@@ -0,0 +1,65 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: HexedHero <[email protected]>
+Date: Thu, 6 May 2021 14:56:43 +0100
+Subject: [PATCH] Add more WanderingTrader API
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
+index 8034588a9a87b907c35e28e220280d463f34554e..a65fba5621c067c453858efb7fee64cbee1e7916 100644
+--- a/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
++++ b/src/main/java/net/minecraft/world/entity/npc/WanderingTrader.java
+@@ -62,6 +62,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
+ @Nullable
+ private BlockPos wanderTarget;
+ private int despawnDelay;
++ // Paper start - Add more WanderingTrader API
++ public boolean canDrinkPotion = true;
++ public boolean canDrinkMilk = true;
++ // Paper end - Add more WanderingTrader API
+
+ public WanderingTrader(EntityType<? extends WanderingTrader> type, Level world) {
+ super(type, world);
+@@ -72,10 +76,10 @@ public class WanderingTrader extends net.minecraft.world.entity.npc.AbstractVill
+ protected void registerGoals() {
+ this.goalSelector.addGoal(0, new FloatGoal(this));
+ this.goalSelector.addGoal(0, new UseItemGoal<>(this, PotionContents.createItemStack(Items.POTION, Potions.INVISIBILITY), SoundEvents.WANDERING_TRADER_DISAPPEARED, (entityvillagertrader) -> {
+- return this.level().isNight() && !entityvillagertrader.isInvisible();
++ return this.canDrinkPotion && this.level().isNight() && !entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API
+ }));
+ this.goalSelector.addGoal(0, new UseItemGoal<>(this, new ItemStack(Items.MILK_BUCKET), SoundEvents.WANDERING_TRADER_REAPPEARED, (entityvillagertrader) -> {
+- return this.level().isDay() && entityvillagertrader.isInvisible();
++ return this.canDrinkMilk && this.level().isDay() && entityvillagertrader.isInvisible(); // Paper - Add more WanderingTrader API
+ }));
+ this.goalSelector.addGoal(1, new TradeWithPlayerGoal(this));
+ this.goalSelector.addGoal(1, new AvoidEntityGoal<>(this, Zombie.class, 8.0F, 0.5D, 0.5D));
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java
+index 08194a78c2170e971ee8ff440b276ed3590e8c4a..0e597394a3dd08f022614fc9777302fea581eb55 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java
+@@ -28,4 +28,26 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande
+ public void setDespawnDelay(int despawnDelay) {
+ this.getHandle().setDespawnDelay(despawnDelay);
+ }
++
++ // Paper start - Add more WanderingTrader API
++ @Override
++ public void setCanDrinkPotion(boolean bool) {
++ getHandle().canDrinkPotion = bool;
++ }
++
++ @Override
++ public boolean canDrinkPotion() {
++ return getHandle().canDrinkPotion;
++ }
++
++ @Override
++ public void setCanDrinkMilk(boolean bool) {
++ getHandle().canDrinkMilk = bool;
++ }
++
++ @Override
++ public boolean canDrinkMilk() {
++ return getHandle().canDrinkMilk;
++ }
++ // Paper end
+ }
diff --git a/patches/server/0517-Add-EntityBlockStorage-clearEntities.patch b/patches/server/0517-Add-EntityBlockStorage-clearEntities.patch
new file mode 100644
index 0000000000..51ffd96ff7
--- /dev/null
+++ b/patches/server/0517-Add-EntityBlockStorage-clearEntities.patch
@@ -0,0 +1,38 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Mon, 5 Apr 2021 18:12:29 -0400
+Subject: [PATCH] Add EntityBlockStorage#clearEntities()
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
+index 333189cf01ce571993e8152f5851b8c362ba4b70..801528022c902714138c264bc4d05ef0df85912e 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/BeehiveBlockEntity.java
+@@ -152,6 +152,11 @@ public class BeehiveBlockEntity extends BlockEntity {
+ return this.stored.size();
+ }
+
++ // Paper start - Add EntityBlockStorage clearEntities
++ public void clearBees() {
++ this.stored.clear();
++ }
++ // Paper end - Add EntityBlockStorage clearEntities
+ public static int getHoneyLevel(BlockState state) {
+ return (Integer) state.getValue(BeehiveBlock.HONEY_LEVEL);
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java
+index fbacbef0dd51037fa0af56fedeea50a5c7bed8c7..1a2a05160ba51d9c75f1ae6ae61d944d81428722 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBeehive.java
+@@ -95,4 +95,11 @@ public class CraftBeehive extends CraftBlockEntityState<BeehiveBlockEntity> impl
+ public CraftBeehive copy(Location location) {
+ return new CraftBeehive(this, location);
+ }
++
++ // Paper start - Add EntityBlockStorage clearEntities
++ @Override
++ public void clearEntities() {
++ getSnapshot().clearBees();
++ }
++ // Paper end
+ }
diff --git a/patches/server/0518-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch b/patches/server/0518-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch
new file mode 100644
index 0000000000..9df7276feb
--- /dev/null
+++ b/patches/server/0518-Add-Adventure-message-to-PlayerAdvancementDoneEvent.patch
@@ -0,0 +1,35 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Alvinn8 <[email protected]>
+Date: Fri, 8 Jan 2021 20:31:13 +0100
+Subject: [PATCH] Add Adventure message to PlayerAdvancementDoneEvent
+
+
+diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java
+index 0fe941b0802f2966ad55509baac124f56ecef999..1dcb8a287be7df2a59b5b4c1345be80637a7f679 100644
+--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java
++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java
+@@ -236,11 +236,21 @@ public class PlayerAdvancements {
+ this.progressChanged.add(advancement);
+ flag = true;
+ if (!flag1 && advancementprogress.isDone()) {
+- this.player.level().getCraftServer().getPluginManager().callEvent(new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.toBukkit())); // CraftBukkit
++ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent
++ final net.kyori.adventure.text.Component message = advancement.value().display().flatMap(info -> {
++ return java.util.Optional.ofNullable(
++ info.shouldAnnounceChat() ? io.papermc.paper.adventure.PaperAdventure.asAdventure(info.getType().createAnnouncement(advancement, this.player)) : null
++ );
++ }).orElse(null);
++ final org.bukkit.event.player.PlayerAdvancementDoneEvent event = new org.bukkit.event.player.PlayerAdvancementDoneEvent(this.player.getBukkitEntity(), advancement.toBukkit(), message);
++ this.player.level().getCraftServer().getPluginManager().callEvent(event); // CraftBukkit
++ // Paper end
+ advancement.value().rewards().grant(this.player);
+ advancement.value().display().ifPresent((advancementdisplay) -> {
+- if (advancementdisplay.shouldAnnounceChat() && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
+- this.playerList.broadcastSystemMessage(advancementdisplay.getType().createAnnouncement(advancement, this.player), false);
++ // Paper start - Add Adventure message to PlayerAdvancementDoneEvent
++ if (event.message() != null && this.player.serverLevel().getGameRules().getBoolean(GameRules.RULE_ANNOUNCE_ADVANCEMENTS)) {
++ this.playerList.broadcastSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.message()), false);
++ // Paper end
+ }
+
+ });
diff --git a/patches/server/0519-Add-HiddenPotionEffect-API.patch b/patches/server/0519-Add-HiddenPotionEffect-API.patch
new file mode 100644
index 0000000000..2d0b0d5b03
--- /dev/null
+++ b/patches/server/0519-Add-HiddenPotionEffect-API.patch
@@ -0,0 +1,29 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Tamion <[email protected]>
+Date: Sun, 5 Nov 2023 09:51:28 +0100
+Subject: [PATCH] Add HiddenPotionEffect API
+
+== AT ==
+public net.minecraft.world.effect.MobEffectInstance hiddenEffect
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java
+index 01af4db5d0f17ea2943e5c464d4122a358503bc1..cb11f0624e4e65aa06bfaaec90729ee536cd53a0 100644
+--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java
++++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionUtil.java
+@@ -78,6 +78,7 @@ public class CraftPotionUtil {
+
+ public static MobEffectInstance fromBukkit(PotionEffect effect) {
+ Holder<MobEffect> type = CraftPotionEffectType.bukkitToMinecraftHolder(effect.getType());
++ // Paper - Note: do not copy over the hidden effect, as this method is only used for applying to entities which we do not want to convert over.
+ return new MobEffectInstance(type, effect.getDuration(), effect.getAmplifier(), effect.isAmbient(), effect.hasParticles(), effect.hasIcon()); // Paper
+ }
+
+@@ -87,7 +88,7 @@ public class CraftPotionUtil {
+ int duration = effect.getDuration();
+ boolean ambient = effect.isAmbient();
+ boolean particles = effect.isVisible();
+- return new PotionEffect(type, duration, amp, ambient, particles, effect.showIcon()); // Paper
++ return new PotionEffect(type, duration, amp, ambient, particles, effect.showIcon(), effect.hiddenEffect == null ? null : toBukkit(effect.hiddenEffect)); // Paper
+ }
+
+ public static boolean equals(Holder<MobEffect> mobEffect, PotionEffectType type) {
diff --git a/patches/server/0520-Inventory-close.patch b/patches/server/0520-Inventory-close.patch
new file mode 100644
index 0000000000..9dcad3d0b1
--- /dev/null
+++ b/patches/server/0520-Inventory-close.patch
@@ -0,0 +1,25 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Tue, 11 May 2021 14:54:56 -0700
+Subject: [PATCH] Inventory#close
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
+index f3a1859a1c2adb0448186c322793585dafefe7e0..bc5ec42a54401a2275c05f0f506ba89f00c19ec9 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
+@@ -439,6 +439,14 @@ public class CraftInventory implements Inventory {
+ this.clear(i);
+ }
+ }
++ // Paper start
++ @Override
++ public int close() {
++ int count = this.inventory.getViewers().size();
++ com.google.common.collect.Lists.newArrayList(this.inventory.getViewers()).forEach(HumanEntity::closeInventory);
++ return count;
++ }
++ // Paper end
+
+ @Override
+ public ListIterator<ItemStack> iterator() {
diff --git a/patches/server/0521-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch b/patches/server/0521-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch
new file mode 100644
index 0000000000..cf84510891
--- /dev/null
+++ b/patches/server/0521-Add-a-should-burn-in-sunlight-API-for-Phantoms-and-S.patch
@@ -0,0 +1,130 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: MeFisto94 <[email protected]>
+Date: Tue, 11 May 2021 00:48:33 +0200
+Subject: [PATCH] Add a "should burn in sunlight" API for Phantoms and
+ Skeletons
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
+index 7c80c79a87ec438d0891c5c977e162f272d80039..cb89a95e6ff9db73c912bba04d27657683135153 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
++++ b/src/main/java/net/minecraft/world/entity/monster/AbstractSkeleton.java
+@@ -97,9 +97,15 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
+
+ abstract SoundEvent getStepSound();
+
++ // Paper start - shouldBurnInDay API
++ private boolean shouldBurnInDay = true;
++ public boolean shouldBurnInDay() { return shouldBurnInDay; }
++ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
++ // Paper end - shouldBurnInDay API
++
+ @Override
+ public void aiStep() {
+- boolean flag = this.isSunBurnTick();
++ boolean flag = shouldBurnInDay && this.isSunBurnTick(); // Paper - shouldBurnInDay API
+
+ if (flag) {
+ ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
+@@ -243,7 +249,20 @@ public abstract class AbstractSkeleton extends Monster implements RangedAttackMo
+ public void readAdditionalSaveData(CompoundTag nbt) {
+ super.readAdditionalSaveData(nbt);
+ this.reassessWeaponGoal();
++ // Paper start - shouldBurnInDay API
++ if (nbt.contains("Paper.ShouldBurnInDay")) {
++ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay");
++ }
++ // Paper end - shouldBurnInDay API
++ }
++
++ // Paper start - shouldBurnInDay API
++ @Override
++ public void addAdditionalSaveData(CompoundTag nbt) {
++ super.addAdditionalSaveData(nbt);
++ nbt.putBoolean("Paper.ShouldBurnInDay", this.shouldBurnInDay);
+ }
++ // Paper end - shouldBurnInDay API
+
+ @Override
+ public void setItemSlot(EquipmentSlot slot, ItemStack stack) {
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Phantom.java b/src/main/java/net/minecraft/world/entity/monster/Phantom.java
+index 1171a4e45bed0455b29b2cf012fbc2883b16d061..4ff75412452649ebf106ef591cb97dc7ac8175e7 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Phantom.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Phantom.java
+@@ -139,7 +139,7 @@ public class Phantom extends FlyingMob implements Enemy {
+
+ @Override
+ public void aiStep() {
+- if (this.isAlive() && this.isSunBurnTick()) {
++ if (this.isAlive() && this.shouldBurnInDay && this.isSunBurnTick()) { // Paper - shouldBurnInDay API
+ this.igniteForSeconds(8.0F);
+ }
+
+@@ -165,6 +165,9 @@ public class Phantom extends FlyingMob implements Enemy {
+ if (nbt.hasUUID("Paper.SpawningEntity")) {
+ this.spawningEntity = nbt.getUUID("Paper.SpawningEntity");
+ }
++ if (nbt.contains("Paper.ShouldBurnInDay")) {
++ this.shouldBurnInDay = nbt.getBoolean("Paper.ShouldBurnInDay");
++ }
+ // Paper end
+ }
+
+@@ -179,6 +182,7 @@ public class Phantom extends FlyingMob implements Enemy {
+ if (this.spawningEntity != null) {
+ nbt.putUUID("Paper.SpawningEntity", this.spawningEntity);
+ }
++ nbt.putBoolean("Paper.ShouldBurnInDay", shouldBurnInDay);
+ // Paper end
+ }
+
+@@ -238,6 +242,9 @@ public class Phantom extends FlyingMob implements Enemy {
+ return this.spawningEntity;
+ }
+ public void setSpawningEntity(java.util.UUID entity) { this.spawningEntity = entity; }
++ private boolean shouldBurnInDay = true;
++ public boolean shouldBurnInDay() { return shouldBurnInDay; }
++ public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; }
+ // Paper end
+
+ private static enum AttackPhase {
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java
+index 5ff566186431440c25a26900aba14e4adb642031..5beaa2bb0d58fe477ce8d2de8b77600d3b416d8c 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractSkeleton.java
+@@ -20,4 +20,15 @@ public abstract class CraftAbstractSkeleton extends CraftMonster implements Abst
+ return (net.minecraft.world.entity.monster.AbstractSkeleton) super.getHandle();
+ }
+ // Paper end
++ // Paper start
++ @Override
++ public boolean shouldBurnInDay() {
++ return getHandle().shouldBurnInDay();
++ }
++
++ @Override
++ public void setShouldBurnInDay(boolean shouldBurnInDay) {
++ getHandle().setShouldBurnInDay(shouldBurnInDay);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java
+index 305a635b049741ac5e2670060c6818cb2c07e5ab..9304e201db1ec96d0916aa8ea781f3e4bc7991e6 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java
+@@ -34,5 +34,15 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy {
+ public java.util.UUID getSpawningEntity() {
+ return getHandle().getSpawningEntity();
+ }
++
++ @Override
++ public boolean shouldBurnInDay() {
++ return getHandle().shouldBurnInDay();
++ }
++
++ @Override
++ public void setShouldBurnInDay(boolean shouldBurnInDay) {
++ getHandle().setShouldBurnInDay(shouldBurnInDay);
++ }
+ // Paper end
+ }
diff --git a/patches/server/0522-Add-basic-Datapack-API.patch b/patches/server/0522-Add-basic-Datapack-API.patch
new file mode 100644
index 0000000000..990697b9ea
--- /dev/null
+++ b/patches/server/0522-Add-basic-Datapack-API.patch
@@ -0,0 +1,209 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Connor Linfoot <[email protected]>
+Date: Sun, 16 May 2021 15:07:34 +0100
+Subject: [PATCH] Add basic Datapack API
+
+Co-authored-by: Jake Potrebic <[email protected]>
+
+diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..8bd8263b51fb2bb364353565b1ba26b3b0d1d55e
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapack.java
+@@ -0,0 +1,103 @@
++package io.papermc.paper.datapack;
++
++import io.papermc.paper.adventure.PaperAdventure;
++import io.papermc.paper.event.server.ServerResourcesReloadedEvent;
++import io.papermc.paper.world.flag.PaperFeatureFlagProviderImpl;
++import java.util.ArrayList;
++import java.util.List;
++import java.util.Map;
++import java.util.Set;
++import java.util.concurrent.ConcurrentHashMap;
++import net.kyori.adventure.text.Component;
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.packs.repository.Pack;
++import net.minecraft.server.packs.repository.PackSource;
++import org.bukkit.FeatureFlag;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public class PaperDatapack implements Datapack {
++
++ private static final Map<PackSource, DatapackSource> PACK_SOURCES = new ConcurrentHashMap<>();
++ static {
++ PACK_SOURCES.put(PackSource.DEFAULT, DatapackSource.DEFAULT);
++ PACK_SOURCES.put(PackSource.BUILT_IN, DatapackSource.BUILT_IN);
++ PACK_SOURCES.put(PackSource.FEATURE, DatapackSource.FEATURE);
++ PACK_SOURCES.put(PackSource.WORLD, DatapackSource.WORLD);
++ PACK_SOURCES.put(PackSource.SERVER, DatapackSource.SERVER);
++ }
++
++ private final Pack pack;
++ private final boolean enabled;
++
++ PaperDatapack(final Pack pack, final boolean enabled) {
++ this.pack = pack;
++ this.enabled = enabled;
++ }
++
++ @Override
++ public String getName() {
++ return this.pack.getId();
++ }
++
++ @Override
++ public Component getTitle() {
++ return PaperAdventure.asAdventure(this.pack.getTitle());
++ }
++
++ @Override
++ public Component getDescription() {
++ return PaperAdventure.asAdventure(this.pack.getDescription());
++ }
++
++ @Override
++ public boolean isRequired() {
++ return this.pack.isRequired();
++ }
++
++ @Override
++ public Compatibility getCompatibility() {
++ return Datapack.Compatibility.valueOf(this.pack.getCompatibility().name());
++ }
++
++ @Override
++ public Set<FeatureFlag> getRequiredFeatures() {
++ return PaperFeatureFlagProviderImpl.fromNms(this.pack.getRequestedFeatures());
++ }
++
++ @Override
++ public boolean isEnabled() {
++ return this.enabled;
++ }
++
++ @Override
++ public void setEnabled(final boolean enabled) {
++ final MinecraftServer server = MinecraftServer.getServer();
++ final List<Pack> enabledPacks = new ArrayList<>(server.getPackRepository().getSelectedPacks());
++ final @Nullable Pack packToChange = server.getPackRepository().getPack(this.getName());
++ if (packToChange == null) {
++ throw new IllegalStateException("Cannot toggle state of pack that doesn't exist: " + this.getName());
++ }
++ if (enabled == enabledPacks.contains(packToChange)) {
++ return;
++ }
++ if (enabled) {
++ packToChange.getDefaultPosition().insert(enabledPacks, packToChange, Pack::selectionConfig, false); // modeled off the default /datapack enable logic
++ } else {
++ enabledPacks.remove(packToChange);
++ }
++ server.reloadResources(enabledPacks.stream().map(Pack::getId).toList(), ServerResourcesReloadedEvent.Cause.PLUGIN);
++ }
++
++ @Override
++ public DatapackSource getSource() {
++ return PACK_SOURCES.computeIfAbsent(this.pack.location().source(), source -> new DatapackSourceImpl(source.toString()));
++ }
++
++ @Override
++ public Component computeDisplayName() {
++ return PaperAdventure.asAdventure(this.pack.getChatLink(this.enabled));
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..caa41c525d2b36b5a9f9942380f06c97d5781a93
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/datapack/PaperDatapackManager.java
+@@ -0,0 +1,55 @@
++package io.papermc.paper.datapack;
++
++import com.google.common.collect.Collections2;
++import java.util.Collection;
++import java.util.Collections;
++import java.util.function.Predicate;
++import net.minecraft.server.packs.repository.Pack;
++import net.minecraft.server.packs.repository.PackRepository;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public class PaperDatapackManager implements DatapackManager {
++
++ private final PackRepository repository;
++
++ public PaperDatapackManager(final PackRepository repository) {
++ this.repository = repository;
++ }
++
++ @Override
++ public void refreshPacks() {
++ this.repository.reload();
++ }
++
++ @Override
++ public @Nullable Datapack getPack(final @NonNull String name) {
++ final @Nullable Pack pack = this.repository.getPack(name);
++ if (pack == null) {
++ return null;
++ }
++ return new PaperDatapack(pack, this.repository.getSelectedPacks().contains(pack));
++ }
++
++ @Override
++ public Collection<Datapack> getPacks() {
++ final Collection<Pack> enabledPacks = this.repository.getSelectedPacks();
++ return this.transformPacks(this.repository.getAvailablePacks(), enabledPacks::contains);
++ }
++
++ @Override
++ public Collection<Datapack> getEnabledPacks() {
++ return this.transformPacks(this.repository.getSelectedPacks(), pack -> true);
++ }
++
++ private Collection<Datapack> transformPacks(final Collection<Pack> packs, final Predicate<Pack> enabled) {
++ return Collections.unmodifiableCollection(
++ Collections2.transform(
++ packs,
++ pack -> new PaperDatapack(pack, enabled.test(pack))
++ )
++ );
++ }
++}
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 3cfacacd1d8fd17ec4b54936afd8124823b1a00b..b4af066a21e4893b5ec146d109b5146b6996a0cf 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -308,6 +308,7 @@ public final class CraftServer implements Server {
+ private final List<CraftPlayer> playerView;
+ public int reloadCount;
+ public Set<String> activeCompatibilities = Collections.emptySet();
++ private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper
+ public static Exception excessiveVelEx; // Paper - Velocity warnings
+
+ static {
+@@ -392,6 +393,7 @@ public final class CraftServer implements Server {
+ if (this.configuration.getBoolean("settings.use-map-color-cache")) {
+ MapPalette.setMapColorCache(new CraftMapColorCache(this.logger));
+ }
++ datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper
+ }
+
+ public boolean getCommandBlockOverride(String command) {
+@@ -3036,5 +3038,11 @@ public final class CraftServer implements Server {
+ public com.destroystokyo.paper.entity.ai.MobGoals getMobGoals() {
+ return mobGoals;
+ }
++
++ @Override
++ public io.papermc.paper.datapack.PaperDatapackManager getDatapackManager() {
++ return datapackManager;
++ }
++
+ // Paper end
+ }
diff --git a/patches/server/0523-Add-environment-variable-to-disable-server-gui.patch b/patches/server/0523-Add-environment-variable-to-disable-server-gui.patch
new file mode 100644
index 0000000000..186cfa260f
--- /dev/null
+++ b/patches/server/0523-Add-environment-variable-to-disable-server-gui.patch
@@ -0,0 +1,18 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Riley Park <[email protected]>
+Date: Mon, 17 May 2021 00:34:55 -0700
+Subject: [PATCH] Add environment variable to disable server gui
+
+
+diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
+index da61f3a4ec9bb9726d9d45552b8eada3f87d9e58..c551f6827476b6432ebe1d48e7ca5d168df305c5 100644
+--- a/src/main/java/net/minecraft/server/Main.java
++++ b/src/main/java/net/minecraft/server/Main.java
+@@ -320,6 +320,7 @@ public class Main {
+ */
+ boolean flag2 = !optionset.has("nogui") && !optionset.nonOptionArguments().contains("nogui");
+
++ if(!Boolean.parseBoolean(System.getenv().getOrDefault("PAPER_DISABLE_SERVER_GUI", String.valueOf(false)))) // Paper - Add environment variable to disable server gui
+ if (flag2 && !GraphicsEnvironment.isHeadless()) {
+ dedicatedserver1.showGui();
+ }
diff --git a/patches/server/0524-Expand-PlayerGameModeChangeEvent.patch b/patches/server/0524-Expand-PlayerGameModeChangeEvent.patch
new file mode 100644
index 0000000000..065d67fdf4
--- /dev/null
+++ b/patches/server/0524-Expand-PlayerGameModeChangeEvent.patch
@@ -0,0 +1,161 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 15 May 2021 10:04:43 -0700
+Subject: [PATCH] Expand PlayerGameModeChangeEvent
+
+
+diff --git a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java
+index b95a979f8f58f43e0becedcd843ff8bb992eb455..a046a0b1519806ff3d987e6402f152b60a3a6f4c 100644
+--- a/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java
++++ b/src/main/java/net/minecraft/server/commands/DefaultGameModeCommands.java
+@@ -28,9 +28,13 @@ public class DefaultGameModeCommands {
+ GameType gameType = minecraftServer.getForcedGameType();
+ if (gameType != null) {
+ for (ServerPlayer serverPlayer : minecraftServer.getPlayerList().getPlayers()) {
+- if (serverPlayer.setGameMode(gameType)) {
+- i++;
++ // Paper start - Expand PlayerGameModeChangeEvent
++ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameType, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, net.kyori.adventure.text.Component.empty());
++ if (event != null && event.isCancelled()) {
++ source.sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), false);
+ }
++ // Paper end - Expand PlayerGameModeChangeEvent
++ i++;
+ }
+ }
+
+diff --git a/src/main/java/net/minecraft/server/commands/GameModeCommand.java b/src/main/java/net/minecraft/server/commands/GameModeCommand.java
+index 7f09119bc7d661e08a960dd2bd46006efe752d3e..d1da3600dc07107309b20ebe6e7c0c4da0e8de76 100644
+--- a/src/main/java/net/minecraft/server/commands/GameModeCommand.java
++++ b/src/main/java/net/minecraft/server/commands/GameModeCommand.java
+@@ -60,9 +60,14 @@ public class GameModeCommand {
+ int i = 0;
+
+ for (ServerPlayer serverPlayer : targets) {
+- if (serverPlayer.setGameMode(gameMode)) {
++ // Paper start - Expand PlayerGameModeChangeEvent
++ org.bukkit.event.player.PlayerGameModeChangeEvent event = serverPlayer.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.COMMAND, net.kyori.adventure.text.Component.empty());
++ if (event != null && !event.isCancelled()) {
+ logGamemodeChange(context.getSource(), serverPlayer, gameMode);
+ i++;
++ } else if (event != null && event.cancelMessage() != null) {
++ context.getSource().sendSuccess(() -> io.papermc.paper.adventure.PaperAdventure.asVanilla(event.cancelMessage()), true);
++ // Paper end - Expand PlayerGameModeChangeEvent
+ }
+ }
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index c22dce8b30c8af3e37b497b0d97c6ceef0295883..2125f9dc87dbf31a66004dc859788f584e099e73 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -2315,10 +2315,18 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ }
+
+ public boolean setGameMode(GameType gameMode) {
++ // Paper start - Expand PlayerGameModeChangeEvent
++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.setGameMode(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null);
++ return event == null ? false : event.isCancelled();
++ }
++ @Nullable
++ public org.bukkit.event.player.PlayerGameModeChangeEvent setGameMode(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component message) {
+ boolean flag = this.isSpectator();
+
+- if (!this.gameMode.changeGameModeForPlayer(gameMode)) {
+- return false;
++ org.bukkit.event.player.PlayerGameModeChangeEvent event = this.gameMode.changeGameModeForPlayer(gameMode, cause, message);
++ if (event == null || event.isCancelled()) {
++ return null;
++ // Paper end - Expand PlayerGameModeChangeEvent
+ } else {
+ this.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.CHANGE_GAME_MODE, (float) gameMode.getId()));
+ if (gameMode == GameType.SPECTATOR) {
+@@ -2334,7 +2342,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+
+ this.onUpdateAbilities();
+ this.updateEffectVisibility();
+- return true;
++ return event; // Paper - Expand PlayerGameModeChangeEvent
+ }
+ }
+
+@@ -2783,6 +2791,16 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ }
+
+ public void loadGameTypes(@Nullable CompoundTag nbt) {
++ // Paper start - Expand PlayerGameModeChangeEvent
++ if (this.server.getForcedGameType() != null && this.server.getForcedGameType() != ServerPlayer.readPlayerMode(nbt, "playerGameType")) {
++ if (new org.bukkit.event.player.PlayerGameModeChangeEvent(this.getBukkitEntity(), org.bukkit.GameMode.getByValue(this.server.getDefaultGameType().getId()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.DEFAULT_GAMEMODE, null).callEvent()) {
++ this.gameMode.setGameModeForPlayer(this.server.getForcedGameType(), GameType.DEFAULT_MODE);
++ } else {
++ this.gameMode.setGameModeForPlayer(ServerPlayer.readPlayerMode(nbt,"playerGameType"), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType"));
++ }
++ return;
++ }
++ // Paper end - Expand PlayerGameModeChangeEvent
+ this.gameMode.setGameModeForPlayer(this.calculateGameModeForNewPlayer(ServerPlayer.readPlayerMode(nbt, "playerGameType")), ServerPlayer.readPlayerMode(nbt, "previousPlayerGameType"));
+ }
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+index 546be40a8e4470fb5a6686072cdd342cdaa6fe15..e000a918230187f6841b03b7b0dd73687f3cc15e 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+@@ -72,14 +72,21 @@ public class ServerPlayerGameMode {
+ }
+
+ public boolean changeGameModeForPlayer(GameType gameMode) {
++ // Paper start - Expand PlayerGameModeChangeEvent
++ PlayerGameModeChangeEvent event = this.changeGameModeForPlayer(gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.UNKNOWN, null);
++ return event != null && event.isCancelled();
++ }
++ @Nullable
++ public PlayerGameModeChangeEvent changeGameModeForPlayer(GameType gameMode, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause cause, @Nullable net.kyori.adventure.text.Component cancelMessage) {
++ // Paper end - Expand PlayerGameModeChangeEvent
+ if (gameMode == this.gameModeForPlayer) {
+- return false;
++ return null; // Paper - Expand PlayerGameModeChangeEvent
+ } else {
+ // CraftBukkit start
+- PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()));
++ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(this.player.getBukkitEntity(), GameMode.getByValue(gameMode.getId()), cause, cancelMessage); // Paper
+ this.level.getCraftServer().getPluginManager().callEvent(event);
+ if (event.isCancelled()) {
+- return false;
++ return event; // Paper - Expand PlayerGameModeChangeEvent
+ }
+ // CraftBukkit end
+ this.setGameModeForPlayer(gameMode, this.previousGameModeForPlayer);
+@@ -90,7 +97,7 @@ public class ServerPlayerGameMode {
+ this.player.resetCurrentImpulseContext();
+ }
+
+- return true;
++ return event; // Paper - Expand PlayerGameModeChangeEvent
+ }
+ }
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 47f925707d8756796ce169b7cbd8f2410b0b7104..f97bbed2b0098fb9b9b0a9e7c2a124e1370608cc 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -2818,7 +2818,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ this.player = this.server.getPlayerList().respawn(this.player, false, Entity.RemovalReason.KILLED, RespawnReason.DEATH); // CraftBukkit
+ this.resetPosition();
+ if (this.server.isHardcore()) {
+- this.player.setGameMode(GameType.SPECTATOR);
++ this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent
+ ((GameRules.BooleanValue) this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.player.serverLevel()); // CraftBukkit - per-world
+ }
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+index c93ec7e97c9af44ed75e7ea4fb1c6c58c2b6bc1a..0b151a66d7419653088526bd72119ebd2d6dd18e 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+@@ -1671,7 +1671,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+ Preconditions.checkArgument(mode != null, "GameMode cannot be null");
+ if (this.getHandle().connection == null) return;
+
+- this.getHandle().setGameMode(GameType.byId(mode.getValue()));
++ this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper - Expand PlayerGameModeChangeEvent
+ }
+
+ @Override
diff --git a/patches/server/0525-ItemStack-repair-check-API.patch b/patches/server/0525-ItemStack-repair-check-API.patch
new file mode 100644
index 0000000000..33c7e11836
--- /dev/null
+++ b/patches/server/0525-ItemStack-repair-check-API.patch
@@ -0,0 +1,72 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 15 May 2021 22:11:11 -0700
+Subject: [PATCH] ItemStack repair check API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+index 30f2ddb66385718304b4b0302f7efcafd54bc42a..fa4e8cb2d53e0ac4e15a8188454ceed0afafe503 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+@@ -548,6 +548,14 @@ public final class CraftMagicNumbers implements UnsafeValues {
+ public int getProtocolVersion() {
+ return net.minecraft.SharedConstants.getCurrentVersion().getProtocolVersion();
+ }
++
++ @Override
++ public boolean isValidRepairItemStack(org.bukkit.inventory.ItemStack itemToBeRepaired, org.bukkit.inventory.ItemStack repairMaterial) {
++ if (!itemToBeRepaired.getType().isItem() || !repairMaterial.getType().isItem()) {
++ return false;
++ }
++ return CraftItemStack.unwrap(itemToBeRepaired).isValidRepairItem(CraftItemStack.unwrap(repairMaterial));
++ }
+ // Paper end
+
+ /**
+diff --git a/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..04e2568816f1fbe090b40e5a55d8d4effc045740
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/util/ItemStackRepairCheckTest.java
+@@ -0,0 +1,41 @@
++package io.papermc.paper.util;
++
++import org.bukkit.Material;
++import org.bukkit.inventory.ItemStack;
++import org.bukkit.support.environment.VanillaFeature;
++import org.junit.jupiter.api.Test;
++
++import static org.junit.jupiter.api.Assertions.assertFalse;
++import static org.junit.jupiter.api.Assertions.assertTrue;
++
++@VanillaFeature
++public class ItemStackRepairCheckTest {
++
++ @Test
++ public void testIsRepariableBy() {
++ ItemStack diamondPick = new ItemStack(Material.DIAMOND_PICKAXE);
++
++ assertTrue(diamondPick.isRepairableBy(new ItemStack(Material.DIAMOND)), "diamond pick isn't repairable by a diamond");
++ }
++
++ @Test
++ public void testCanRepair() {
++ ItemStack diamond = new ItemStack(Material.DIAMOND);
++
++ assertTrue(diamond.canRepair(new ItemStack(Material.DIAMOND_AXE)), "diamond can't repair a diamond axe");
++ }
++
++ @Test
++ public void testIsNotRepairableBy() {
++ ItemStack notDiamondPick = new ItemStack(Material.ACACIA_SAPLING);
++
++ assertFalse(notDiamondPick.isRepairableBy(new ItemStack(Material.DIAMOND)), "acacia sapling is repairable by a diamond");
++ }
++
++ @Test
++ public void testCanNotRepair() {
++ ItemStack diamond = new ItemStack(Material.DIAMOND);
++
++ assertFalse(diamond.canRepair(new ItemStack(Material.OAK_BUTTON)), "diamond can repair oak button");
++ }
++}
diff --git a/patches/server/0526-More-Enchantment-API.patch b/patches/server/0526-More-Enchantment-API.patch
new file mode 100644
index 0000000000..a3bf8f8e7e
--- /dev/null
+++ b/patches/server/0526-More-Enchantment-API.patch
@@ -0,0 +1,105 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 6 May 2021 19:57:58 -0700
+Subject: [PATCH] More Enchantment API
+
+== AT ==
+public net.minecraft.world.item.enchantment.Enchantment definition
+
+Co-authored-by: Luis <[email protected]>
+Co-authored-by: Janet Blackquill <[email protected]>
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
+index 59c9c970b83f62245d860994c4ac0c21dcc15398..4221a1e9cba35c8dc58e51e162e7fcbd0e8b31af 100644
+--- a/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
++++ b/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java
+@@ -5,6 +5,7 @@ import java.util.Locale;
+ import net.minecraft.Util;
+ import net.minecraft.core.Holder;
+ import net.minecraft.core.registries.Registries;
++import net.minecraft.network.chat.contents.TranslatableContents;
+ import net.minecraft.tags.EnchantmentTags;
+ import org.bukkit.NamespacedKey;
+ import org.bukkit.Registry;
+@@ -90,7 +91,7 @@ public class CraftEnchantment extends Enchantment implements Handleable<net.mine
+
+ @Override
+ public boolean isTreasure() {
+- return !this.handle.is(EnchantmentTags.IN_ENCHANTING_TABLE);
++ return this.handle.is(EnchantmentTags.TREASURE); // Paper - use treasure tag
+ }
+
+ @Override
+@@ -148,7 +149,7 @@ public class CraftEnchantment extends Enchantment implements Handleable<net.mine
+ // Paper start
+ @Override
+ public net.kyori.adventure.text.Component displayName(int level) {
+- return io.papermc.paper.adventure.PaperAdventure.asAdventure(getHandle().getFullname(level));
++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(net.minecraft.world.item.enchantment.Enchantment.getFullname(this.handle, level));
+ }
+ // Paper end
+
+@@ -159,10 +160,62 @@ public class CraftEnchantment extends Enchantment implements Handleable<net.mine
+ throw new UnsupportedOperationException("Description isn't translatable!"); // Paper
+ }
+ return translatableContents.getKey();
+-
+ }
+ // Paper end - add translationKey methods
+
++ // Paper start - more Enchantment API
++ @Override
++ public boolean isTradeable() {
++ return this.handle.is(EnchantmentTags.TRADEABLE);
++ }
++
++ @Override
++ public boolean isDiscoverable() {
++ return this.handle.is(EnchantmentTags.IN_ENCHANTING_TABLE)
++ || this.handle.is(EnchantmentTags.ON_RANDOM_LOOT)
++ || this.handle.is(EnchantmentTags.ON_MOB_SPAWN_EQUIPMENT)
++ || this.handle.is(EnchantmentTags.TRADEABLE)
++ || this.handle.is(EnchantmentTags.ON_TRADED_EQUIPMENT);
++ }
++
++ @Override
++ public int getMinModifiedCost(int level) {
++ return this.getHandle().definition().minCost().calculate(level);
++ }
++
++ @Override
++ public int getMaxModifiedCost(int level) {
++ return this.getHandle().definition().maxCost().calculate(level);
++ }
++
++ @Override
++ public int getAnvilCost() {
++ return this.getHandle().definition().anvilCost();
++ }
++
++ @Override
++ public io.papermc.paper.enchantments.EnchantmentRarity getRarity() {
++ throw new UnsupportedOperationException("Enchantments don't have a rarity anymore in 1.20.5+.");
++ }
++
++ @Override
++ public float getDamageIncrease(int level, org.bukkit.entity.EntityCategory entityCategory) {
++ throw new UnsupportedOperationException("Enchantments are based on complex effect maps since 1.21, cannot compute a simple damage increase");
++ }
++
++ @Override
++ public float getDamageIncrease(int level, org.bukkit.entity.EntityType entityType) {
++ throw new UnsupportedOperationException("Enchantments are based on complex effect maps since 1.21, cannot compute a simple damage increase");
++ }
++
++ @Override
++ public java.util.Set<org.bukkit.inventory.EquipmentSlotGroup> getActiveSlotGroups() {
++ return this.getHandle().definition().slots().stream()
++ .map(org.bukkit.craftbukkit.CraftEquipmentSlot::getSlot)
++ .collect(java.util.stream.Collectors.toSet());
++ }
++ // Paper end - more Enchantment API
++
+ @Override
+ public String getTranslationKey() {
+ return Util.makeDescriptionId("enchantment", this.handle.unwrapKey().get().location());
diff --git a/patches/server/0527-Move-range-check-for-block-placing-up.patch b/patches/server/0527-Move-range-check-for-block-placing-up.patch
new file mode 100644
index 0000000000..fb8c43feb0
--- /dev/null
+++ b/patches/server/0527-Move-range-check-for-block-placing-up.patch
@@ -0,0 +1,22 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Wed, 8 Jun 2022 10:52:18 +0200
+Subject: [PATCH] Move range check for block placing up
+
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index f97bbed2b0098fb9b9b0a9e7c2a124e1370608cc..10376b24e3a09c023a73a21a4499415589795423 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -1814,6 +1814,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ if (itemstack.isItemEnabled(worldserver.enabledFeatures())) {
+ BlockHitResult movingobjectpositionblock = packet.getHitResult();
+ Vec3 vec3d = movingobjectpositionblock.getLocation();
++ // Paper start - improve distance check
++ if (!Double.isFinite(vec3d.x) || !Double.isFinite(vec3d.y) || !Double.isFinite(vec3d.z)) {
++ return;
++ }
++ // Paper end - improve distance check
+ BlockPos blockposition = movingobjectpositionblock.getBlockPos();
+
+ if (this.player.canInteractWithBlock(blockposition, 1.0D)) {
diff --git a/patches/server/0528-Add-Mob-lookAt-API.patch b/patches/server/0528-Add-Mob-lookAt-API.patch
new file mode 100644
index 0000000000..8a8ba9fdcd
--- /dev/null
+++ b/patches/server/0528-Add-Mob-lookAt-API.patch
@@ -0,0 +1,64 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath <[email protected]>
+Date: Fri, 14 May 2021 13:42:17 -0500
+Subject: [PATCH] Add Mob#lookAt API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
+index c67673772c876dab7c015dcdfb75b297d3c4fbad..bd739428a7e5e35ebcdb70cd187379b3d222339b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
+@@ -97,5 +97,53 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob {
+ public boolean isInDaylight() {
+ return getHandle().isSunBurnTick();
+ }
++
++ @Override
++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location) {
++ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null");
++ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world");
++ getHandle().getLookControl().setLookAt(location.getX(), location.getY(), location.getZ());
++ }
++
++ @Override
++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.Location location, float headRotationSpeed, float maxHeadPitch) {
++ com.google.common.base.Preconditions.checkNotNull(location, "location cannot be null");
++ com.google.common.base.Preconditions.checkArgument(location.getWorld().equals(getWorld()), "location in a different world");
++ getHandle().getLookControl().setLookAt(location.getX(), location.getY(), location.getZ(), headRotationSpeed, maxHeadPitch);
++ }
++
++ @Override
++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity) {
++ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null");
++ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world");
++ getHandle().getLookControl().setLookAt(((CraftEntity) entity).getHandle());
++ }
++
++ @Override
++ public void lookAt(@org.jetbrains.annotations.NotNull org.bukkit.entity.Entity entity, float headRotationSpeed, float maxHeadPitch) {
++ com.google.common.base.Preconditions.checkNotNull(entity, "entity cannot be null");
++ com.google.common.base.Preconditions.checkArgument(entity.getWorld().equals(getWorld()), "entity in a different world");
++ getHandle().getLookControl().setLookAt(((CraftEntity) entity).getHandle(), headRotationSpeed, maxHeadPitch);
++ }
++
++ @Override
++ public void lookAt(double x, double y, double z) {
++ getHandle().getLookControl().setLookAt(x, y, z);
++ }
++
++ @Override
++ public void lookAt(double x, double y, double z, float headRotationSpeed, float maxHeadPitch) {
++ getHandle().getLookControl().setLookAt(x, y, z, headRotationSpeed, maxHeadPitch);
++ }
++
++ @Override
++ public int getHeadRotationSpeed() {
++ return getHandle().getHeadRotSpeed();
++ }
++
++ @Override
++ public int getMaxHeadPitch() {
++ return getHandle().getMaxHeadXRot();
++ }
+ // Paper end
+ }
diff --git a/patches/server/0529-Correctly-check-if-bucket-dispenses-will-succeed-for.patch b/patches/server/0529-Correctly-check-if-bucket-dispenses-will-succeed-for.patch
new file mode 100644
index 0000000000..cc05cc8b4e
--- /dev/null
+++ b/patches/server/0529-Correctly-check-if-bucket-dispenses-will-succeed-for.patch
@@ -0,0 +1,28 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Mon, 1 Jan 2024 12:57:19 -0800
+Subject: [PATCH] Correctly check if bucket dispenses will succeed for event
+
+Upstream incorrectly checks if the bucket place will succeed
+in order to fire the BlockDispenseEvent. This patch corrects
+that.
+
+diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
+index ab002c2a5cfb029bd61b7d8e0548fd135b95cdcc..780f83d50aac70c819608f4c79c08ef34664d7b0 100644
+--- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
++++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
+@@ -330,7 +330,13 @@ public interface DispenseItemBehavior {
+ int y = blockposition.getY();
+ int z = blockposition.getZ();
+ BlockState iblockdata = worldserver.getBlockState(blockposition);
+- if (iblockdata.isAir() || iblockdata.canBeReplaced() || (dispensiblecontaineritem instanceof BucketItem && iblockdata.getBlock() instanceof LiquidBlockContainer && ((LiquidBlockContainer) iblockdata.getBlock()).canPlaceLiquid((Player) null, worldserver, blockposition, iblockdata, ((BucketItem) dispensiblecontaineritem).content))) {
++ // Paper start - correctly check if the bucket place will succeed
++ /* Taken from SolidBucketItem#emptyContents */
++ boolean willEmptyContentsSolidBucketItem = dispensiblecontaineritem instanceof net.minecraft.world.item.SolidBucketItem && worldserver.isInWorldBounds(blockposition) && iblockdata.isAir();
++ /* Taken from BucketItem#emptyContents */
++ boolean willEmptyBucketItem = dispensiblecontaineritem instanceof final BucketItem bucketItem && bucketItem.content instanceof net.minecraft.world.level.material.FlowingFluid && (iblockdata.isAir() || iblockdata.canBeReplaced(bucketItem.content) || (iblockdata.getBlock() instanceof LiquidBlockContainer liquidBlockContainer && liquidBlockContainer.canPlaceLiquid(null, worldserver, blockposition, iblockdata, bucketItem.content)));
++ if (willEmptyContentsSolidBucketItem || willEmptyBucketItem) {
++ // Paper end - correctly check if the bucket place will succeed
+ org.bukkit.block.Block block = CraftBlock.at(worldserver, pointer.pos());
+ CraftItemStack craftItem = CraftItemStack.asCraftMirror(stack);
+
diff --git a/patches/server/0530-Add-Unix-domain-socket-support.patch b/patches/server/0530-Add-Unix-domain-socket-support.patch
new file mode 100644
index 0000000000..08bcf8abe1
--- /dev/null
+++ b/patches/server/0530-Add-Unix-domain-socket-support.patch
@@ -0,0 +1,137 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Andrew Steinborn <[email protected]>
+Date: Tue, 11 May 2021 17:39:22 -0400
+Subject: [PATCH] Add Unix domain socket support
+
+
+diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+index 761663514b98a1c0a0a905150411aff450a0cc50..21d6f728d6ecd35a05933e9406a386c36135a456 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -233,6 +233,20 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ this.setEnforceWhitelist(dedicatedserverproperties.enforceWhitelist);
+ // this.worldData.setGameType(dedicatedserverproperties.gamemode); // CraftBukkit - moved to world loading
+ DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode);
++ // Paper start - Unix domain socket support
++ java.net.SocketAddress bindAddress;
++ if (this.getLocalIp().startsWith("unix:")) {
++ if (!io.netty.channel.epoll.Epoll.isAvailable()) {
++ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!");
++ DedicatedServer.LOGGER.error("You are trying to use a Unix domain socket but you're not on a supported OS.");
++ return false;
++ } else if (!io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && !org.spigotmc.SpigotConfig.bungee) {
++ DedicatedServer.LOGGER.error("**** INVALID CONFIGURATION!");
++ DedicatedServer.LOGGER.error("Unix domain sockets require IPs to be forwarded from a proxy.");
++ return false;
++ }
++ bindAddress = new io.netty.channel.unix.DomainSocketAddress(this.getLocalIp().substring("unix:".length()));
++ } else {
+ InetAddress inetaddress = null;
+
+ if (!this.getLocalIp().isEmpty()) {
+@@ -242,12 +256,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ if (this.getPort() < 0) {
+ this.setPort(dedicatedserverproperties.serverPort);
+ }
++ bindAddress = new java.net.InetSocketAddress(inetaddress, this.getPort());
++ }
++ // Paper end - Unix domain socket support
+
+ this.initializeKeyPair();
+ DedicatedServer.LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort());
+
+ try {
+- this.getConnection().startTcpServerListener(inetaddress, this.getPort());
++ this.getConnection().bind(bindAddress); // Paper - Unix domain socket support
+ } catch (IOException ioexception) {
+ DedicatedServer.LOGGER.warn("**** FAILED TO BIND TO PORT!");
+ DedicatedServer.LOGGER.warn("The exception was: {}", ioexception.toString());
+diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+index d6d7f1c446ba5507f67038ff27775ba75156f4a7..c63c194c44646e6bc1a59426552787011fc2ced5 100644
+--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+@@ -76,7 +76,12 @@ public class ServerConnectionListener {
+ this.running = true;
+ }
+
++ // Paper start - Unix domain socket support
+ public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException {
++ bind(new java.net.InetSocketAddress(address, port));
++ }
++ public void bind(java.net.SocketAddress address) throws IOException {
++ // Paper end - Unix domain socket support
+ List list = this.channels;
+
+ synchronized (this.channels) {
+@@ -84,7 +89,13 @@ public class ServerConnectionListener {
+ EventLoopGroup eventloopgroup;
+
+ if (Epoll.isAvailable() && this.server.isEpollEnabled()) {
++ // Paper start - Unix domain socket support
++ if (address instanceof io.netty.channel.unix.DomainSocketAddress) {
++ oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class;
++ } else {
+ oclass = EpollServerSocketChannel.class;
++ }
++ // Paper end - Unix domain socket support
+ eventloopgroup = (EventLoopGroup) ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP.get();
+ ServerConnectionListener.LOGGER.info("Using epoll channel type");
+ } else {
+@@ -117,7 +128,7 @@ public class ServerConnectionListener {
+ ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object));
+ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners
+ }
+- }).group(eventloopgroup).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit
++ }).group(eventloopgroup).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper - Unix domain socket support
+ }
+ }
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 10376b24e3a09c023a73a21a4499415589795423..979f2aa4716cd5ddc35e2d81a3de82af4b4c296e 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -2631,6 +2631,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ // Spigot Start
+ public SocketAddress getRawAddress()
+ {
++ // Paper start - Unix domain socket support; this can be nullable in the case of a Unix domain socket, so if it is, fake something
++ if (connection.channel.remoteAddress() == null) {
++ return new java.net.InetSocketAddress(java.net.InetAddress.getLoopbackAddress(), 0);
++ }
++ // Paper end - Unix domain socket support
+ return this.connection.channel.remoteAddress();
+ }
+ // Spigot End
+diff --git a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
+index a5bbea6a073e00c10c3c5facd997eb8473fd9a5f..ddf42645402afefc0f5caebc684b191eef9d6ec2 100644
+--- a/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
+@@ -81,6 +81,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL
+ this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND);
+ // CraftBukkit start - Connection throttle
+ try {
++ if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - Unix domain socket support; the connection throttle is useless when you have a Unix domain socket
+ long currentTime = System.currentTimeMillis();
+ long connectionThrottle = this.server.server.getConnectionThrottle();
+ InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress();
+@@ -109,6 +110,7 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL
+ }
+ }
+ }
++ } // Paper - Unix domain socket support
+ } catch (Throwable t) {
+ org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t);
+ }
+@@ -166,8 +168,11 @@ public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketL
+ if (!handledByEvent && proxyLogicEnabled) { // Paper
+ // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above!
+ if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper - Add bypass host check
++ // Paper start - Unix domain socket support
++ java.net.SocketAddress socketAddress = this.connection.getRemoteAddress();
+ this.connection.hostname = split[0];
+- this.connection.address = new java.net.InetSocketAddress(split[1], ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getPort());
++ this.connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0);
++ // Paper end - Unix domain socket support
+ this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] );
+ } else
+ {
diff --git a/patches/server/0531-Add-EntityInsideBlockEvent.patch b/patches/server/0531-Add-EntityInsideBlockEvent.patch
new file mode 100644
index 0000000000..cf9fee7e14
--- /dev/null
+++ b/patches/server/0531-Add-EntityInsideBlockEvent.patch
@@ -0,0 +1,294 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 8 May 2021 18:02:36 -0700
+Subject: [PATCH] Add EntityInsideBlockEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java
+index 7f0811bc22d78cdc0aca4c6869c90252d8a59eed..c8ca41cd81a72f9bff40f5c1b3bfc1189bf51f98 100644
+--- a/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/BaseFireBlock.java
+@@ -128,6 +128,7 @@ public abstract class BaseFireBlock extends Block {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (!entity.fireImmune()) {
+ if (entity.getRemainingFireTicks() < 0) {
+ entity.setRemainingFireTicks(entity.getRemainingFireTicks() + 1);
+diff --git a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
+index cd3d9e69653afba51a55a3dfc6764b712b8df859..9afa811579ac2e556b5c5c23b3b49587439dfadc 100644
+--- a/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/BasePressurePlateBlock.java
+@@ -77,6 +77,7 @@ public abstract class BasePressurePlateBlock extends Block {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (!world.isClientSide) {
+ int i = this.getSignalForState(state);
+
+diff --git a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java
+index 87083f7de53e32713b54315803ccd414db2debc8..9e3f1441d62128535112621bf259c24f1a90595b 100644
+--- a/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/BigDripleafBlock.java
+@@ -180,6 +180,7 @@ public class BigDripleafBlock extends HorizontalDirectionalBlock implements Bone
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (!world.isClientSide) {
+ if (state.getValue(BigDripleafBlock.TILT) == Tilt.NONE && BigDripleafBlock.canEntityTilt(pos, entity) && !world.hasNeighborSignal(pos)) {
+ // CraftBukkit start - tilt dripleaf
+diff --git a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java
+index c2d8caee4acb878aaa43c0cdc6f6a37555b69a12..385da0585f409ee453f10d45f5837cdc09adc21b 100644
+--- a/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/BubbleColumnBlock.java
+@@ -48,6 +48,7 @@ public class BubbleColumnBlock extends Block implements BucketPickup {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ BlockState blockState = world.getBlockState(pos.above());
+ if (blockState.isAir()) {
+ entity.onAboveBubbleCol(state.getValue(DRAG_DOWN));
+diff --git a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java
+index e210c210b733fc968b9c3816bb1f9755d76660ef..061a8f8b58d9fa7959333e2f59d3b7ee03cbf92d 100644
+--- a/src/main/java/net/minecraft/world/level/block/ButtonBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/ButtonBlock.java
+@@ -208,6 +208,7 @@ public class ButtonBlock extends FaceAttachedHorizontalDirectionalBlock {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (!world.isClientSide && this.type.canButtonBeActivatedByArrows() && !(Boolean) state.getValue(ButtonBlock.POWERED)) {
+ this.checkPressed(state, world, pos);
+ }
+diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java
+index de1b64e0cbe7f2de63f04262428c9e6ec340916e..c045b1cccf0047dbef8c04d5a28d31d53389054f 100644
+--- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java
+@@ -122,6 +122,7 @@ public class CactusBlock extends Block {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ entity.hurt(world.damageSources().cactus().directBlock(world, pos), 1.0F); // CraftBukkit
+ }
+
+diff --git a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java
+index e03e2f1d84b4f12d78974b90eb4b741818cdac17..1b94f26e78db062f80d806b82f714a815b4710ff 100644
+--- a/src/main/java/net/minecraft/world/level/block/CampfireBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/CampfireBlock.java
+@@ -112,6 +112,7 @@ public class CampfireBlock extends BaseEntityBlock implements SimpleWaterloggedB
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if ((Boolean) state.getValue(CampfireBlock.LIT) && entity instanceof LivingEntity) {
+ entity.hurt(world.damageSources().campfire().directBlock(world, pos), (float) this.fireDamage); // CraftBukkit
+ }
+diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java
+index 91dd550de860fd16585218814eae0b9dc877c77d..6fe896079c0ae622976c2055f8d13cda5ed3ea3d 100644
+--- a/src/main/java/net/minecraft/world/level/block/CropBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java
+@@ -174,6 +174,7 @@ public class CropBlock extends BushBlock implements BonemealableBlock {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (world instanceof ServerLevel worldserver) {
+ if (entity instanceof Ravager && CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState(), !worldserver.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
+ worldserver.destroyBlock(pos, true, entity);
+diff --git a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java
+index 1e2f56b5c40c3dc72bc38354160f8e7de1f4f5cf..fa1c4defd0d4e4cd888eb26eed131539d0ed573f 100644
+--- a/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/DetectorRailBlock.java
+@@ -52,6 +52,7 @@ public class DetectorRailBlock extends BaseRailBlock {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (!world.isClientSide) {
+ if (!(Boolean) state.getValue(DetectorRailBlock.POWERED)) {
+ this.checkPressed(world, pos, state);
+diff --git a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java
+index b27b09fcb895b72c51335bdcb095f0f3bd3a190b..58b2454ac5bd3400559aba3c64f228f41f6218ae 100644
+--- a/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/EndGatewayBlock.java
+@@ -92,6 +92,7 @@ public class EndGatewayBlock extends BaseEntityBlock implements Portal {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (entity.canUsePortal(false)) {
+ BlockEntity tileentity = world.getBlockEntity(pos);
+
+diff --git a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java
+index 8887d35d188510cf10da3dc46b0b56373ac346bd..8ca226641588a88c8b068a7acac9d7e92b077144 100644
+--- a/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/EndPortalBlock.java
+@@ -68,6 +68,7 @@ public class EndPortalBlock extends BaseEntityBlock implements Portal {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (entity.canUsePortal(false)) {
+ // CraftBukkit start - Entity in portal
+ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()));
+diff --git a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java
+index aee71779f31def5f1ef7438cf06219d1de7092ec..34be6b349722240e99f91d28067578aa0b4bd1fe 100644
+--- a/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/FrogspawnBlock.java
+@@ -89,6 +89,7 @@ public class FrogspawnBlock extends Block {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (entity.getType().equals(EntityType.FALLING_BLOCK)) {
+ this.destroyBlock(world, pos);
+ }
+diff --git a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java
+index 3ddf43a8afe8f00ca910d7838356dfc6d007a4f9..5c360c6768582c1a35431739613e9b406875cc21 100644
+--- a/src/main/java/net/minecraft/world/level/block/HoneyBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/HoneyBlock.java
+@@ -60,6 +60,7 @@ public class HoneyBlock extends HalfTransparentBlock {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (this.isSlidingDown(pos, entity)) {
+ this.maybeDoSlideAchievement(entity, pos);
+ this.doSlideMovement(entity);
+diff --git a/src/main/java/net/minecraft/world/level/block/HopperBlock.java b/src/main/java/net/minecraft/world/level/block/HopperBlock.java
+index 882e7e6c7fa77eef109f8203f22edfa9536a96f8..9da02e643948aaea91307e28eb83177aa5d0ecec 100644
+--- a/src/main/java/net/minecraft/world/level/block/HopperBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/HopperBlock.java
+@@ -178,6 +178,7 @@ public class HopperBlock extends BaseEntityBlock {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ BlockEntity blockEntity = world.getBlockEntity(pos);
+ if (blockEntity instanceof HopperBlockEntity) {
+ HopperBlockEntity.entityInside(world, pos, state, entity, (HopperBlockEntity)blockEntity);
+diff --git a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java
+index 7bf2c33a194517d4e52511fe32a8434cbed0361f..d29a62775913922ffb8e3c58ae0db7e37f77226e 100644
+--- a/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/LavaCauldronBlock.java
+@@ -32,6 +32,7 @@ public class LavaCauldronBlock extends AbstractCauldronBlock {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (this.isEntityInsideContent(state, pos, entity)) {
+ entity.lavaHurt();
+ }
+diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java
+index c088d713e80f16ead333ea5283f7f95a2de08523..4ab73a083eba2ad3e12526af0a0dbcfba5cf6c14 100644
+--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java
+@@ -67,6 +67,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (world instanceof ServerLevel worldserver) {
+ if (entity.isOnFire() && this.isEntityInsideContent(state, pos, entity)) {
+ // CraftBukkit start - moved down
+diff --git a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java
+index e404722c119631e31c0519111ccd5f1682c2638d..151025a407a07271bc205955f0ce06f84231563b 100644
+--- a/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/NetherPortalBlock.java
+@@ -115,6 +115,7 @@ public class NetherPortalBlock extends Block implements Portal {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (entity.canUsePortal(false)) {
+ // CraftBukkit start - Entity in portal
+ EntityPortalEnterEvent event = new EntityPortalEnterEvent(entity.getBukkitEntity(), new org.bukkit.Location(world.getWorld(), pos.getX(), pos.getY(), pos.getZ()));
+diff --git a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java
+index 3825b25c7417e4c2e5a25154879199b155a4921f..972d8833127090c01d620cab10b3eca3d3601710 100644
+--- a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java
+@@ -107,6 +107,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl
+
+ @Override
+ public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (world instanceof ServerLevel serverLevel && entity instanceof Ravager && serverLevel.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) {
+ serverLevel.destroyBlock(pos, true, entity);
+ }
+diff --git a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java
+index aaf9350a096022c87ccb788d657c1ae6a7b53a47..53f1a7ed6b4bd6e2d8460531226aabf249994c02 100644
+--- a/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/PowderSnowBlock.java
+@@ -59,6 +59,7 @@ public class PowderSnowBlock extends Block implements BucketPickup {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (!(entity instanceof LivingEntity) || entity.getInBlockState().is((Block) this)) {
+ entity.makeStuckInBlock(state, new Vec3(0.8999999761581421D, 1.5D, 0.8999999761581421D));
+ if (world.isClientSide) {
+diff --git a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java
+index a044a2d7e5cb7027394c95c495c59530bc5e18ce..265c85413da04dbc7292a0af934a8b665b2fced5 100644
+--- a/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/SweetBerryBushBlock.java
+@@ -84,6 +84,7 @@ public class SweetBerryBushBlock extends BushBlock implements BonemealableBlock
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (entity instanceof LivingEntity && entity.getType() != EntityType.FOX && entity.getType() != EntityType.BEE) {
+ entity.makeStuckInBlock(state, new Vec3(0.800000011920929D, 0.75D, 0.800000011920929D));
+ if (world instanceof ServerLevel) {
+diff --git a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java
+index 93777f83cdd2de30ccf597fadd8418853954d1ce..f079e5a9aa098225acf09ed9b4aa7ddbc2381270 100644
+--- a/src/main/java/net/minecraft/world/level/block/TripWireBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/TripWireBlock.java
+@@ -141,6 +141,7 @@ public class TripWireBlock extends Block {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (!world.isClientSide) {
+ if (!(Boolean) state.getValue(TripWireBlock.POWERED)) {
+ this.checkPressed(world, pos, List.of(entity));
+diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java
+index 34599b88011aff718f5a6373229489ea3d947ff7..674d710ff88db5eced9e017284d1b7ec7a4fe7cd 100644
+--- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java
+@@ -33,6 +33,7 @@ public class WaterlilyBlock extends BushBlock {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ super.entityInside(state, world, pos, entity);
+ if (world instanceof ServerLevel && entity instanceof AbstractBoat) {
+ // CraftBukkit start
+diff --git a/src/main/java/net/minecraft/world/level/block/WebBlock.java b/src/main/java/net/minecraft/world/level/block/WebBlock.java
+index 4b621793da3d6fbc44f90df863b099ba992930fb..fc209fab3ed1ccb35706a5529ec23ad8b902e491 100644
+--- a/src/main/java/net/minecraft/world/level/block/WebBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/WebBlock.java
+@@ -24,6 +24,7 @@ public class WebBlock extends Block {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ Vec3 vec3 = new Vec3(0.25, 0.05F, 0.25);
+ if (entity instanceof LivingEntity livingEntity && livingEntity.hasEffect(MobEffects.WEAVING)) {
+ vec3 = new Vec3(0.5, 0.25, 0.5);
+diff --git a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java
+index b42924ebdd0f5369151b8a37f0070dec7f402073..8b79b2b7766d873b7e689309d936982fc1724686 100644
+--- a/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/WitherRoseBlock.java
+@@ -63,6 +63,7 @@ public class WitherRoseBlock extends FlowerBlock {
+
+ @Override
+ protected void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (!new io.papermc.paper.event.entity.EntityInsideBlockEvent(entity.getBukkitEntity(), org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)).callEvent()) { return; } // Paper - Add EntityInsideBlockEvent
+ if (world instanceof ServerLevel worldserver) {
+ if (world.getDifficulty() != Difficulty.PEACEFUL && entity instanceof LivingEntity entityliving) {
+ if (!entityliving.isInvulnerableTo(worldserver, world.damageSources().wither())) {
diff --git a/patches/server/0532-Improve-item-default-attribute-API.patch b/patches/server/0532-Improve-item-default-attribute-API.patch
new file mode 100644
index 0000000000..4ded5398f8
--- /dev/null
+++ b/patches/server/0532-Improve-item-default-attribute-API.patch
@@ -0,0 +1,80 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 8 May 2021 15:01:54 -0700
+Subject: [PATCH] Improve item default attribute API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java
+index de0eba19c0c963adb4f17cea22333240021fd801..3b171a08bd0bedfe224905feb5838d2540199bce 100644
+--- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java
++++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java
+@@ -75,7 +75,7 @@ public class CraftAttributeInstance implements AttributeInstance {
+ return new AttributeModifier(CraftNamespacedKey.fromMinecraft(nms.id()), nms.amount(), AttributeModifier.Operation.values()[nms.operation().ordinal()], org.bukkit.inventory.EquipmentSlotGroup.ANY);
+ }
+
+- public static AttributeModifier convert(net.minecraft.world.entity.ai.attributes.AttributeModifier nms, EquipmentSlot slot) {
+- return new AttributeModifier(CraftNamespacedKey.fromMinecraft(nms.id()), nms.amount(), AttributeModifier.Operation.values()[nms.operation().ordinal()], slot.getGroup());
++ public static AttributeModifier convert(net.minecraft.world.entity.ai.attributes.AttributeModifier nms, net.minecraft.world.entity.EquipmentSlotGroup slot) { // Paper
++ return new AttributeModifier(CraftNamespacedKey.fromMinecraft(nms.id()), nms.amount(), AttributeModifier.Operation.values()[nms.operation().ordinal()], org.bukkit.craftbukkit.CraftEquipmentSlot.getSlot(slot)); // Paper
+ }
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java
+index 68756419ac6ee292db9569eab380a5c14d748002..6d76cc1db3ac3f1ae74c13511937fb86082a0b3d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemType.java
+@@ -197,16 +197,33 @@ public class CraftItemType<M extends ItemMeta> implements ItemType.Typed<M>, Han
+ // return CraftEquipmentSlot.getSlot(EntityInsentient.getEquipmentSlotForItem(CraftItemStack.asNMSCopy(ItemStack.of(this))));
+ // }
+
++ // Paper start - improve default attribute API
++ @Override
++ public @NotNull Multimap<Attribute, AttributeModifier> getDefaultAttributeModifiers() {
++ return this.getDefaultAttributeModifiers(sg -> true);
++ }
++ // Paper end - improve default attribute API
++
+ @Override
+ public Multimap<Attribute, AttributeModifier> getDefaultAttributeModifiers(EquipmentSlot slot) {
++ // Paper start - improve/fix item default attribute API
++ final net.minecraft.world.entity.EquipmentSlot nmsSlot = CraftEquipmentSlot.getNMS(slot);
++ return this.getDefaultAttributeModifiers(sg -> sg.test(nmsSlot));
++ }
++
++ private Multimap<Attribute, AttributeModifier> getDefaultAttributeModifiers(final java.util.function.Predicate<net.minecraft.world.entity.EquipmentSlotGroup> slotPredicate) {
++ // Paper end - improve/fix item default attribute API
+ ImmutableMultimap.Builder<Attribute, AttributeModifier> defaultAttributes = ImmutableMultimap.builder();
+
+ ItemAttributeModifiers nmsDefaultAttributes = this.item.components().getOrDefault(DataComponents.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.EMPTY);
+-
+- nmsDefaultAttributes.forEach(CraftEquipmentSlot.getNMS(slot), (key, value) -> {
+- Attribute attribute = CraftAttribute.minecraftToBukkit(key.value());
+- defaultAttributes.put(attribute, CraftAttributeInstance.convert(value, slot));
+- });
++ // Paper start - improve/fix item default attribute API
++ for (final net.minecraft.world.item.component.ItemAttributeModifiers.Entry entry : nmsDefaultAttributes.modifiers()) {
++ if (!slotPredicate.test(entry.slot())) continue;
++ final Attribute attribute = CraftAttribute.minecraftHolderToBukkit(entry.attribute());
++ final AttributeModifier modifier = CraftAttributeInstance.convert(entry.modifier(), entry.slot());
++ defaultAttributes.put(attribute, modifier);
++ }
++ // Paper end - improve/fix item default attribute API
+
+ return defaultAttributes.build();
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+index fa4e8cb2d53e0ac4e15a8188454ceed0afafe503..e613ea0eba9a1d162b8f7dfe32c9c31d82f17dd2 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+@@ -386,7 +386,11 @@ public final class CraftMagicNumbers implements UnsafeValues {
+
+ @Override
+ public Multimap<Attribute, AttributeModifier> getDefaultAttributeModifiers(Material material, EquipmentSlot slot) {
+- return material.getDefaultAttributeModifiers(slot);
++ // Paper start - delegate to method on ItemType
++ final org.bukkit.inventory.ItemType item = material.asItemType();
++ Preconditions.checkArgument(item != null, material + " is not an item and does not have default attributes");
++ return item.getDefaultAttributeModifiers(slot);
++ // Paper end - delegate to method on ItemType
+ }
+
+ @Override
diff --git a/patches/server/0533-Add-cause-to-Weather-ThunderChangeEvents.patch b/patches/server/0533-Add-cause-to-Weather-ThunderChangeEvents.patch
new file mode 100644
index 0000000000..6293b65e40
--- /dev/null
+++ b/patches/server/0533-Add-cause-to-Weather-ThunderChangeEvents.patch
@@ -0,0 +1,118 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 2 Dec 2020 18:23:26 -0800
+Subject: [PATCH] Add cause to Weather/ThunderChangeEvents
+
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index dc2b8d8e62cf1405191aa4fc8d4fa548c7249032..c05ccab70bd81d536f93352f30aab9b07b90876a 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -435,8 +435,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ this.serverLevelData.setClearWeatherTime(clearDuration);
+ this.serverLevelData.setRainTime(rainDuration);
+ this.serverLevelData.setThunderTime(rainDuration);
+- this.serverLevelData.setRaining(raining);
+- this.serverLevelData.setThundering(thundering);
++ this.serverLevelData.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents
++ this.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.COMMAND); // Paper - Add cause to Weather/ThunderChangeEvents
+ }
+
+ @Override
+@@ -852,8 +852,8 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ this.serverLevelData.setThunderTime(j);
+ this.serverLevelData.setRainTime(k);
+ this.serverLevelData.setClearWeatherTime(i);
+- this.serverLevelData.setThundering(flag1);
+- this.serverLevelData.setRaining(flag2);
++ this.serverLevelData.setThundering(flag1, org.bukkit.event.weather.ThunderChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents
++ this.serverLevelData.setRaining(flag2, org.bukkit.event.weather.WeatherChangeEvent.Cause.NATURAL); // Paper - Add cause to Weather/ThunderChangeEvents
+ }
+
+ this.oThunderLevel = this.thunderLevel;
+@@ -920,14 +920,14 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ @VisibleForTesting
+ public void resetWeatherCycle() {
+ // CraftBukkit start
+- this.serverLevelData.setRaining(false);
++ this.serverLevelData.setRaining(false, org.bukkit.event.weather.WeatherChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents
+ // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
+ // Not that everyone ever manages to get the whole server to sleep at the same time....
+ if (!this.serverLevelData.isRaining()) {
+ this.serverLevelData.setRainTime(0);
+ }
+ // CraftBukkit end
+- this.serverLevelData.setThundering(false);
++ this.serverLevelData.setThundering(false, org.bukkit.event.weather.ThunderChangeEvent.Cause.SLEEP); // Paper - Add cause to Weather/ThunderChangeEvents
+ // CraftBukkit start
+ // If we stop due to everyone sleeping we should reset the weather duration to some other random value.
+ // Not that everyone ever manages to get the whole server to sleep at the same time....
+diff --git a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java
+index 6a3959095e57f76b3a092b32d26ff91cf1c5e068..0fa16ff37f09ecfda104b751e48bf246820afc98 100644
+--- a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java
++++ b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java
+@@ -337,6 +337,11 @@ public class PrimaryLevelData implements ServerLevelData, WorldData {
+
+ @Override
+ public void setThundering(boolean thundering) {
++ // Paper start - Add cause to Weather/ThunderChangeEvents
++ this.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.UNKNOWN);
++ }
++ public void setThundering(boolean thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause cause) {
++ // Paper end - Add cause to Weather/ThunderChangeEvents
+ // CraftBukkit start
+ if (this.thundering == thundering) {
+ return;
+@@ -344,7 +349,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData {
+
+ org.bukkit.World world = Bukkit.getWorld(this.getLevelName());
+ if (world != null) {
+- ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering);
++ ThunderChangeEvent thunder = new ThunderChangeEvent(world, thundering, cause); // Paper - Add cause to Weather/ThunderChangeEvents
+ Bukkit.getServer().getPluginManager().callEvent(thunder);
+ if (thunder.isCancelled()) {
+ return;
+@@ -371,6 +376,12 @@ public class PrimaryLevelData implements ServerLevelData, WorldData {
+
+ @Override
+ public void setRaining(boolean raining) {
++ // Paper start - Add cause to Weather/ThunderChangeEvents
++ this.setRaining(raining, org.bukkit.event.weather.WeatherChangeEvent.Cause.UNKNOWN);
++ }
++
++ public void setRaining(boolean raining, org.bukkit.event.weather.WeatherChangeEvent.Cause cause) {
++ // Paper end - Add cause to Weather/ThunderChangeEvents
+ // CraftBukkit start
+ if (this.raining == raining) {
+ return;
+@@ -378,7 +389,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData {
+
+ org.bukkit.World world = Bukkit.getWorld(this.getLevelName());
+ if (world != null) {
+- WeatherChangeEvent weather = new WeatherChangeEvent(world, raining);
++ WeatherChangeEvent weather = new WeatherChangeEvent(world, raining, cause); // Paper - Add cause to Weather/ThunderChangeEvents
+ Bukkit.getServer().getPluginManager().callEvent(weather);
+ if (weather.isCancelled()) {
+ return;
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index f20717ee4505d28fa44f0f9ced5c71ae75c8a6d9..691d65bca79d503a43f696d92a0a32226cddaad5 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -1201,7 +1201,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+
+ @Override
+ public void setStorm(boolean hasStorm) {
+- this.world.levelData.setRaining(hasStorm);
++ this.world.serverLevelData.setRaining(hasStorm, org.bukkit.event.weather.WeatherChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents
+ this.setWeatherDuration(0); // Reset weather duration (legacy behaviour)
+ this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands)
+ }
+@@ -1223,7 +1223,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+
+ @Override
+ public void setThundering(boolean thundering) {
+- this.world.serverLevelData.setThundering(thundering);
++ this.world.serverLevelData.setThundering(thundering, org.bukkit.event.weather.ThunderChangeEvent.Cause.PLUGIN); // Paper - Add cause to Weather/ThunderChangeEvents
+ this.setThunderDuration(0); // Reset weather duration (legacy behaviour)
+ this.setClearWeatherDuration(0); // Reset clear weather duration (reset "/weather clear" commands)
+ }
diff --git a/patches/server/0534-More-Lidded-Block-API.patch b/patches/server/0534-More-Lidded-Block-API.patch
new file mode 100644
index 0000000000..437434c525
--- /dev/null
+++ b/patches/server/0534-More-Lidded-Block-API.patch
@@ -0,0 +1,79 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: LemonCaramel <[email protected]>
+Date: Sun, 23 May 2021 17:49:51 +0900
+Subject: [PATCH] More Lidded Block API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java
+index f4b480e3041fc79060c5fa6ce517047104b280d5..6063f0e1fdc232d063105971359ae688168a2bc4 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBarrel.java
+@@ -73,4 +73,11 @@ public class CraftBarrel extends CraftLootable<BarrelBlockEntity> implements Bar
+ public CraftBarrel copy(Location location) {
+ return new CraftBarrel(this, location);
+ }
++
++ // Paper start - More Lidded Block API
++ @Override
++ public boolean isOpen() {
++ return getTileEntity().openersCounter.opened;
++ }
++ // Paper end - More Lidded Block API
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
+index 2b6a93a944b27290745278957a3577772b7b8212..6e98a00d526b734992ce39b15768c5820dce4ca8 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftChest.java
+@@ -92,4 +92,11 @@ public class CraftChest extends CraftLootable<ChestBlockEntity> implements Chest
+ public CraftChest copy(Location location) {
+ return new CraftChest(this, location);
+ }
++
++ // Paper start - More Lidded Block API
++ @Override
++ public boolean isOpen() {
++ return getTileEntity().openersCounter.opened;
++ }
++ // Paper end - More Lidded Block API
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java
+index 07b63ce5f5e152f6a644134989ffa03af8a12cdf..b64adbba3e52d32d439e64a243cb74f3fbca2ce3 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftEnderChest.java
+@@ -51,4 +51,11 @@ public class CraftEnderChest extends CraftBlockEntityState<EnderChestBlockEntity
+ public CraftEnderChest copy(Location location) {
+ return new CraftEnderChest(this, location);
+ }
++
++ // Paper start - More Lidded Block API
++ @Override
++ public boolean isOpen() {
++ return getTileEntity().openersCounter.opened;
++ }
++ // Paper end - More Lidded Block API
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftShulkerBox.java b/src/main/java/org/bukkit/craftbukkit/block/CraftShulkerBox.java
+index 7676313493bcb6327c2a9c026645fe060733c6ac..f7b199fbc7a740de3ee6952ce12ef2c35f057d7a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftShulkerBox.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftShulkerBox.java
+@@ -59,7 +59,7 @@ public class CraftShulkerBox extends CraftLootable<ShulkerBoxBlockEntity> implem
+ if (this.getTileEntity().opened && this.getWorldHandle() instanceof net.minecraft.world.level.Level) {
+ net.minecraft.world.level.Level world = this.getTileEntity().getLevel();
+ world.blockEvent(this.getPosition(), this.getTileEntity().getBlockState().getBlock(), 1, 0);
+- world.playSound(null, this.getPosition(), SoundEvents.SHULKER_BOX_OPEN, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F);
++ world.playSound(null, this.getPosition(), SoundEvents.SHULKER_BOX_CLOSE, SoundSource.BLOCKS, 0.5F, world.random.nextFloat() * 0.1F + 0.9F); // Paper - More Lidded Block API (Wrong sound)
+ }
+ this.getTileEntity().opened = false;
+ }
+@@ -73,4 +73,11 @@ public class CraftShulkerBox extends CraftLootable<ShulkerBoxBlockEntity> implem
+ public CraftShulkerBox copy(Location location) {
+ return new CraftShulkerBox(this, location);
+ }
++
++ // Paper start - More Lidded Block API
++ @Override
++ public boolean isOpen() {
++ return getTileEntity().opened;
++ }
++ // Paper end - More Lidded Block API
+ }
diff --git a/patches/server/0535-Limit-item-frame-cursors-on-maps.patch b/patches/server/0535-Limit-item-frame-cursors-on-maps.patch
new file mode 100644
index 0000000000..a55a4490c0
--- /dev/null
+++ b/patches/server/0535-Limit-item-frame-cursors-on-maps.patch
@@ -0,0 +1,30 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Yive <[email protected]>
+Date: Wed, 26 May 2021 15:09:33 -0700
+Subject: [PATCH] Limit item frame cursors on maps
+
+
+diff --git a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+index a89f0b652c515efbc24ffc9af47fa65082946e54..2d5e7380e8a14cbc01ba48cd05deccc0c7f53430 100644
+--- a/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
++++ b/src/main/java/net/minecraft/world/level/saveddata/maps/MapItemSavedData.java
+@@ -326,8 +326,10 @@ public class MapItemSavedData extends SavedData {
+
+ MapFrame worldmapframe1 = new MapFrame(blockposition, entityitemframe.getDirection().get2DDataValue() * 90, entityitemframe.getId());
+
++ if (this.decorations.size() < player.level().paperConfig().maps.itemFrameCursorLimit) { // Paper - Limit item frame cursors on maps
+ this.addDecoration(MapDecorationTypes.FRAME, player.level(), MapItemSavedData.getFrameKey(entityitemframe.getId()), (double) blockposition.getX(), (double) blockposition.getZ(), (double) (entityitemframe.getDirection().get2DDataValue() * 90), (Component) null);
+ this.frameMarkers.put(worldmapframe1.getId(), worldmapframe1);
++ } // Paper - Limit item frame cursors on maps
+ }
+
+ MapDecorations mapdecorations = (MapDecorations) stack.getOrDefault(DataComponents.MAP_DECORATIONS, MapDecorations.EMPTY);
+@@ -520,7 +522,7 @@ public class MapItemSavedData extends SavedData {
+ return true;
+ }
+
+- if (!this.isTrackedCountOverLimit(256)) {
++ if (!this.isTrackedCountOverLimit(((Level) world).paperConfig().maps.itemFrameCursorLimit)) { // Paper - Limit item frame cursors on maps
+ this.bannerMarkers.put(mapiconbanner.getId(), mapiconbanner);
+ this.addDecoration(mapiconbanner.getDecoration(), world, mapiconbanner.getId(), d0, d1, 180.0D, (Component) mapiconbanner.name().orElse(null)); // CraftBukkit - decompile error
+ return true;
diff --git a/patches/server/0536-Add-PlayerKickEvent-causes.patch b/patches/server/0536-Add-PlayerKickEvent-causes.patch
new file mode 100644
index 0000000000..25c4735557
--- /dev/null
+++ b/patches/server/0536-Add-PlayerKickEvent-causes.patch
@@ -0,0 +1,548 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 15 May 2021 20:30:45 -0700
+Subject: [PATCH] Add PlayerKickEvent causes
+
+
+diff --git a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java
+index dbcf183483766f39334d7f7e8336033906625f3f..300929a406905f5ff1ede664d5b99fb0938d4d2e 100644
+--- a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java
++++ b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java
+@@ -40,14 +40,14 @@ public class SignedMessageChain {
+ if (signature == null) {
+ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.MISSING_PROFILE_KEY);
+ } else if (playerPublicKey.data().hasExpired()) {
+- throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.EXPIRED_PROFILE_KEY);
++ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.EXPIRED_PROFILE_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes
+ } else {
+ SignedMessageLink signedMessageLink = SignedMessageChain.this.nextLink;
+ if (signedMessageLink == null) {
+ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.CHAIN_BROKEN);
+ } else if (body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) {
+ this.setChainBroken();
+- throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT);
++ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT, org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes
+ } else {
+ SignedMessageChain.this.lastTimeStamp = body.timeStamp();
+ PlayerChatMessage playerChatMessage = new PlayerChatMessage(signedMessageLink, signature, body, null, FilterMask.PASS_THROUGH);
+@@ -80,8 +80,15 @@ public class SignedMessageChain {
+ static final Component INVALID_SIGNATURE = Component.translatable("chat.disabled.invalid_signature");
+ static final Component OUT_OF_ORDER_CHAT = Component.translatable("chat.disabled.out_of_order_chat");
+
+- public DecodeException(Component message) {
++ // Paper start
++ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause;
++ public DecodeException(Component message, org.bukkit.event.player.PlayerKickEvent.Cause event) {
+ super(message);
++ this.kickCause = event;
++ }
++ // Paper end
++ public DecodeException(Component message) {
++ this(message, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); // Paper
+ }
+ }
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 0ab92fb362729108beca6bc0230fb6fb06ca6280..edd2e83df282b0e24d4c7e3a34776a5b039c2c6b 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -2325,7 +2325,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(net.kyori.adventure.text.Component.text(org.spigotmc.SpigotConfig.whitelistMessage));
++ entityplayer.connection.disconnect(net.kyori.adventure.text.Component.text(org.spigotmc.SpigotConfig.whitelistMessage), org.bukkit.event.player.PlayerKickEvent.Cause.WHITELIST); // Paper - use configurable message & kick event cause
+ }
+ }
+
+diff --git a/src/main/java/net/minecraft/server/commands/BanIpCommands.java b/src/main/java/net/minecraft/server/commands/BanIpCommands.java
+index b451f98aaa5b694cfd9fadebcb5a4441951e9e87..b23dca02fe85a299d13353706915db2aec3467a6 100644
+--- a/src/main/java/net/minecraft/server/commands/BanIpCommands.java
++++ b/src/main/java/net/minecraft/server/commands/BanIpCommands.java
+@@ -66,7 +66,7 @@ public class BanIpCommands {
+ }
+
+ for (ServerPlayer serverPlayer : list) {
+- serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.ip_banned"));
++ serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.ip_banned"), org.bukkit.event.player.PlayerKickEvent.Cause.IP_BANNED); // Paper - kick event cause
+ }
+
+ return list.size();
+diff --git a/src/main/java/net/minecraft/server/commands/BanPlayerCommands.java b/src/main/java/net/minecraft/server/commands/BanPlayerCommands.java
+index e63a03a419061edc6a1305f6469d2282d960d6d1..be436480873ac914d67dac36061ac087b7389ab1 100644
+--- a/src/main/java/net/minecraft/server/commands/BanPlayerCommands.java
++++ b/src/main/java/net/minecraft/server/commands/BanPlayerCommands.java
+@@ -55,7 +55,7 @@ public class BanPlayerCommands {
+ );
+ ServerPlayer serverPlayer = source.getServer().getPlayerList().getPlayer(gameProfile.getId());
+ if (serverPlayer != null) {
+- serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.banned"));
++ serverPlayer.connection.disconnect(Component.translatable("multiplayer.disconnect.banned"), org.bukkit.event.player.PlayerKickEvent.Cause.BANNED); // Paper - kick event cause
+ }
+ }
+ }
+diff --git a/src/main/java/net/minecraft/server/commands/KickCommand.java b/src/main/java/net/minecraft/server/commands/KickCommand.java
+index d1caaecfdfba1cdeba032f0bc38c06541fa61633..6468b3a25c7527a2fde6899e4812b5cb79ce4b1d 100644
+--- a/src/main/java/net/minecraft/server/commands/KickCommand.java
++++ b/src/main/java/net/minecraft/server/commands/KickCommand.java
+@@ -48,7 +48,7 @@ public class KickCommand {
+
+ for (ServerPlayer serverPlayer : targets) {
+ if (!source.getServer().isSingleplayerOwner(serverPlayer.getGameProfile())) {
+- serverPlayer.connection.disconnect(reason);
++ serverPlayer.connection.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.KICK_COMMAND); // Paper - kick event cause
+ source.sendSuccess(() -> Component.translatable("commands.kick.success", serverPlayer.getDisplayName(), reason), true);
+ i++;
+ }
+diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+index f8ae8c8eff73e4e87eb34d0f2635517f1688a6f1..59d20fd62e850a38380d877cef95ed69cb46ecbd 100644
+--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+@@ -63,8 +63,8 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ }
+
+ @Override
+- public void kickPlayer(Component reason) {
+- this.disconnect(reason);
++ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes
++ this.disconnect(reason, cause); // Paper - kick event causes
+ }
+ // CraftBukkit end
+ private static final Logger LOGGER = LogUtils.getLogger();
+@@ -140,7 +140,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ } else if (!this.isSingleplayerOwner()) {
+ // Paper start - This needs to be handled on the main thread for plugins
+ server.submit(() -> {
+- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE);
++ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
+ });
+ // Paper end - This needs to be handled on the main thread for plugins
+ }
+@@ -176,7 +176,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ }
+ } catch (Exception ex) {
+ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex);
+- this.disconnect(Component.literal("Invalid payload REGISTER!"));
++ this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
+ }
+ } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) {
+ try {
+@@ -186,7 +186,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ }
+ } catch (Exception ex) {
+ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex);
+- this.disconnect(Component.literal("Invalid payload UNREGISTER!"));
++ this.disconnect(Component.literal("Invalid payload UNREGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
+ }
+ } else {
+ try {
+@@ -204,7 +204,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data);
+ } catch (Exception ex) {
+ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex);
+- this.disconnect(Component.literal("Invalid custom payload!"));
++ this.disconnect(Component.literal("Invalid custom payload!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause
+ }
+ }
+
+@@ -220,7 +220,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server);
+ if (packet.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) {
+ ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id());
+- this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect"));
++ this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause
+ }
+ // Paper start - adventure pack callbacks
+ // call the callbacks before the previously-existing event so the event has final say
+@@ -250,7 +250,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ return;
+ }
+ // CraftBukkit end
+- this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY);
++ this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY, PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause
+ }
+
+ protected void keepConnectionAlive() {
+@@ -262,7 +262,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+
+ if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets
+ if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected
+- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE);
++ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
+ } else if (this.checkIfClosed(currentTime)) { // Paper
+ this.keepAlivePending = true;
+ this.keepAliveTime = currentTime;
+@@ -278,7 +278,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ private boolean checkIfClosed(long time) {
+ if (this.closed) {
+ if (time - this.closedListenerTime >= 15000L) {
+- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE);
++ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause
+ }
+
+ return false;
+@@ -330,15 +330,25 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+
+ // Paper start - adventure
+ public void disconnect(final net.kyori.adventure.text.Component reason) {
+- this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason));
++ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN);
++ }
++ public void disconnect(final net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) {
++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause);
++ // Paper end - kick event causes
+ }
+ // Paper end - adventure
+
++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - kick event causes
+ public void disconnect(Component reason) {
+- this.disconnect(new DisconnectionDetails(reason));
++ // Paper start - kick event causes
++ this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN);
++ }
++ public void disconnect(final Component reason, PlayerKickEvent.Cause cause) {
++ this.disconnect(new DisconnectionDetails(reason), cause);
++ // Paper end - kick event causes
+ }
+
+- public void disconnect(DisconnectionDetails disconnectionInfo) {
++ public void disconnect(DisconnectionDetails disconnectionInfo, PlayerKickEvent.Cause cause) { // Paper - kick event cause
+ // CraftBukkit start - fire PlayerKickEvent
+ if (this.processedDisconnect) {
+ return;
+@@ -347,7 +357,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ Waitable waitable = new Waitable() {
+ @Override
+ protected Object evaluate() {
+- ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo);
++ ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause); // Paper - kick event causes
+ return null;
+ }
+ };
+@@ -366,7 +376,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+
+ net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure
+
+- PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionInfo.reason()), leaveMessage); // Paper - adventure
++ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionInfo.reason()), leaveMessage, cause); // Paper - adventure & kick event causes
+
+ if (this.cserver.getServer().isRunning()) {
+ this.cserver.getPluginManager().callEvent(event);
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 979f2aa4716cd5ddc35e2d81a3de82af4b4c296e..fffefbed068a339f0db607d6734be611b273bbcf 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -361,7 +361,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) {
+ if (++this.aboveGroundTickCount > this.getMaximumFlyingTicks(this.player)) {
+ ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString());
+- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer); // Paper - use configurable kick message
++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause
+ return;
+ }
+ } else {
+@@ -380,7 +380,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ if (this.clientVehicleIsFloating && this.lastVehicle.getControllingPassenger() == this.player) {
+ if (++this.aboveGroundVehicleTickCount > this.getMaximumFlyingTicks(this.lastVehicle)) {
+ ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString());
+- this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle); // Paper - use configurable kick message
++ this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause
+ return;
+ }
+ } else {
+@@ -400,7 +400,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ this.dropSpamThrottler.tick();
+ if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) {
+ this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854
+- this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"));
++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause
+ }
+
+ }
+@@ -488,7 +488,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+ if (ServerGamePacketListenerImpl.containsInvalidValues(packet.position().x(), packet.position().y(), packet.position().z(), packet.yRot(), packet.xRot())) {
+- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"));
++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause
+ } else if (!this.updateAwaitingTeleport() && this.player.hasClientLoaded()) {
+ Entity entity = this.player.getRootVehicle();
+
+@@ -687,7 +687,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+ if (packet.getId() == this.awaitingTeleport) {
+ if (this.awaitingPositionFromClient == null) {
+- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"));
++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
+ return;
+ }
+
+@@ -754,7 +754,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async
+ // CraftBukkit start
+ if (!this.tabSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { // Paper - configurable tab spam limits
+- this.disconnect(Component.translatable("disconnect.spam"));
++ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - Kick event cause
+ return;
+ }
+ // CraftBukkit end
+@@ -1178,14 +1178,14 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+
+ if (byteTotal > byteAllowed) {
+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to send a book too large. Book size: {} - Allowed: {} - Pages: {}", this.player.getScoreboardName(), byteTotal, byteAllowed, pageList.size());
+- this.disconnect(Component.literal("Book too large!"));
++ this.disconnect(Component.literal("Book too large!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause
+ return;
+ }
+ }
+ // Paper end - Book size limits
+ // CraftBukkit start
+ if (this.lastBookTick + 20 > MinecraftServer.currentTick) {
+- this.disconnect(Component.literal("Book edited too quickly!"));
++ this.disconnect(Component.literal("Book edited too quickly!"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause
+ return;
+ }
+ this.lastBookTick = MinecraftServer.currentTick;
+@@ -1294,7 +1294,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ public void handleMovePlayer(ServerboundMovePlayerPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+ if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) {
+- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"));
++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause
+ } else {
+ ServerLevel worldserver = this.player.serverLevel();
+
+@@ -1734,7 +1734,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ this.dropCount++;
+ if (this.dropCount >= 20) {
+ ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!");
+- this.disconnect(Component.literal("You dropped your items too quickly (Hacking?)"));
++ this.disconnect(Component.literal("You dropped your items too quickly (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // Paper - kick event cause
+ return;
+ }
+ }
+@@ -2036,7 +2036,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ this.player.resetLastActionTime();
+ } else {
+ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString());
+- this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)")); // CraftBukkit
++ this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause
+ }
+ }
+
+@@ -2234,7 +2234,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+
+ private void tryHandleChat(String s, Runnable runnable, boolean sync) { // CraftBukkit
+ if (ServerGamePacketListenerImpl.isChatMessageIllegal(s)) {
+- this.disconnect((Component) Component.translatable("multiplayer.disconnect.illegal_characters"));
++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper
+ } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales
+ this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false));
+ } else {
+@@ -2257,7 +2257,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+
+ if (optional.isEmpty()) {
+ ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString());
+- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED);
++ this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes
+ }
+
+ return optional;
+@@ -2438,7 +2438,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ // this.chatSpamThrottler.increment();
+ if (!this.chatSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) {
+ // CraftBukkit end
+- this.disconnect((Component) Component.translatable("disconnect.spam"));
++ this.disconnect((Component) Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause
+ }
+
+ }
+@@ -2450,7 +2450,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ synchronized (this.lastSeenMessages) {
+ if (!this.lastSeenMessages.applyOffset(packet.offset())) {
+ ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString());
+- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED);
++ this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes
+ }
+
+ }
+@@ -2603,7 +2603,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ }
+
+ if (i > 4096) {
+- this.disconnect((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats"));
++ this.disconnect((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause
+ }
+
+ }
+@@ -2662,7 +2662,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ // Spigot Start
+ if ( entity == this.player && !this.player.isSpectator() )
+ {
+- this.disconnect( Component.literal( "Cannot interact with self!" ) );
++ this.disconnect( Component.literal( "Cannot interact with self!" ), org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - kick event cause
+ return;
+ }
+ // Spigot End
+@@ -2778,7 +2778,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ }
+ }
+
+- ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked"));
++ ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause
+ ServerGamePacketListenerImpl.LOGGER.warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString());
+ }
+ });
+@@ -3178,7 +3178,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ // Paper start - auto recipe limit
+ if (!org.bukkit.Bukkit.isPrimaryThread()) {
+ if (!this.recipeSpamPackets.isIncrementAndUnderThreshold()) {
+- this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam"));
++ this.disconnect(net.minecraft.network.chat.Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause
+ return;
+ }
+ }
+@@ -3440,7 +3440,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+
+ if (!Objects.equals(profilepublickey_a, profilepublickey_a1)) {
+ if (profilepublickey_a != null && profilepublickey_a1.expiresAt().isBefore(profilepublickey_a.expiresAt())) {
+- this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY);
++ this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes
+ } else {
+ try {
+ SignatureValidator signaturevalidator = this.server.getProfileKeySignatureValidator();
+@@ -3453,7 +3453,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator));
+ } catch (ProfilePublicKey.ValidationException profilepublickey_b) {
+ ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage());
+- this.disconnect(profilepublickey_b.getComponent());
++ this.disconnect(profilepublickey_b.getComponent(), profilepublickey_b.kickCause); // Paper - kick event causes
+ }
+
+ }
+diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+index 9d5723cdfdbf6257a71e57842aea9ba317fc049a..1e4b288f20153ce0c91fabf164c5c8320c90ba7d 100644
+--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+@@ -70,7 +70,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
+ }
+
+ @Override
+- public void kickPlayer(Component reason) {
++ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes - during login, no event can be called.
+ this.disconnect(reason);
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 6ddcc928a49630e3a0f7f40cca496642419efa2c..26ba0cec3a8492d91df894a69cc1fc8076eeda0d 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -632,7 +632,7 @@ public abstract class PlayerList {
+ while (iterator.hasNext()) {
+ entityplayer = (ServerPlayer) iterator.next();
+ this.save(entityplayer); // CraftBukkit - Force the player's inventory to be saved
+- entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"));
++ entityplayer.connection.disconnect(Component.translatable("multiplayer.disconnect.duplicate_login"), org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause
+ }
+
+ // Instead of kicking then returning, we need to store the kick reason
+@@ -1236,7 +1236,7 @@ public abstract class PlayerList {
+ // Paper end
+ // CraftBukkit start - disconnect safely
+ for (ServerPlayer player : this.players) {
+- if (isRestarting) player.connection.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.restartMessage)); else // Paper
++ if (isRestarting) player.connection.disconnect(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.restartMessage), org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); else // Paper - kick event cause (cause is never used here)
+ player.connection.disconnect(java.util.Objects.requireNonNullElseGet(this.server.server.shutdownMessage(), net.kyori.adventure.text.Component::empty)); // CraftBukkit - add custom shutdown message // Paper - Adventure
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java b/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java
+index 9e2ad78b12cadbf0e2bda1e12fe844120529c347..6a7d7fad990fc44fdda6849d43dad141e61f7f37 100644
+--- a/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java
++++ b/src/main/java/net/minecraft/world/entity/player/ProfilePublicKey.java
+@@ -24,7 +24,7 @@ public record ProfilePublicKey(ProfilePublicKey.Data data) {
+
+ public static ProfilePublicKey createValidated(SignatureValidator servicesSignatureVerifier, UUID playerUuid, ProfilePublicKey.Data publicKeyData) throws ProfilePublicKey.ValidationException {
+ if (!publicKeyData.validateSignature(servicesSignatureVerifier, playerUuid)) {
+- throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE);
++ throw new ProfilePublicKey.ValidationException(INVALID_SIGNATURE, org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PUBLIC_KEY_SIGNATURE); // Paper - kick event causes
+ } else {
+ return new ProfilePublicKey(publicKeyData);
+ }
+@@ -88,8 +88,16 @@ public record ProfilePublicKey(ProfilePublicKey.Data data) {
+ }
+
+ public static class ValidationException extends ThrowingComponent {
++ public final org.bukkit.event.player.PlayerKickEvent.Cause kickCause; // Paper
++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper
+ public ValidationException(Component messageText) {
++ // Paper start
++ this(messageText, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN);
++ }
++ public ValidationException(Component messageText, org.bukkit.event.player.PlayerKickEvent.Cause kickCause) {
++ // Paper end
+ super(messageText);
++ this.kickCause = kickCause; // Paper
+ }
+ }
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+index 0b151a66d7419653088526bd72119ebd2d6dd18e..ff8d6a0ceb41258541c0049464dc0923ed4872bd 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+@@ -280,7 +280,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+
+ void sendPacket(Packet<?> packet);
+
+- void kickPlayer(Component reason);
++ void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause); // Paper - kick event causes
+ }
+
+ public record CookieFuture(ResourceLocation key, CompletableFuture<byte[]> future) {
+@@ -640,7 +640,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+ @Override
+ public void kickPlayer(String message) {
+ org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot
+- this.getHandle().transferCookieConnection.kickPlayer(CraftChatMessage.fromStringOrEmpty(message, true));
++ this.getHandle().transferCookieConnection.kickPlayer(CraftChatMessage.fromStringOrEmpty(message, true), org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause
+ }
+
+ // Paper start
+@@ -652,10 +652,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+
+ @Override
+ public void kick(final net.kyori.adventure.text.Component message) {
++ kick(message, org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN);
++ }
++
++ @Override
++ public void kick(net.kyori.adventure.text.Component message, org.bukkit.event.player.PlayerKickEvent.Cause cause) {
+ org.spigotmc.AsyncCatcher.catchOp("player kick");
+ final ServerGamePacketListenerImpl connection = this.getHandle().connection;
+ if (connection != null) {
+- connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message);
++ connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message, cause);
+ }
+ }
+
+@@ -716,7 +721,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+
+ // Paper start - Improve chat handling
+ if (ServerGamePacketListenerImpl.isChatMessageIllegal(msg)) {
+- this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"));
++ this.getHandle().connection.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - kick event causes
+ } else {
+ if (msg.startsWith("/")) {
+ this.getHandle().connection.handleCommand(msg);
+diff --git a/src/main/java/org/spigotmc/RestartCommand.java b/src/main/java/org/spigotmc/RestartCommand.java
+index 824c4ad135ea5177f416687c7042639ed126b70b..39e56b95aaafbcd8ebe68fdefaace83702e9510d 100644
+--- a/src/main/java/org/spigotmc/RestartCommand.java
++++ b/src/main/java/org/spigotmc/RestartCommand.java
+@@ -74,7 +74,7 @@ public class RestartCommand extends Command
+ // Kick all players
+ for ( ServerPlayer p : com.google.common.collect.ImmutableList.copyOf( MinecraftServer.getServer().getPlayerList().players ) )
+ {
+- p.connection.disconnect( CraftChatMessage.fromStringOrEmpty( SpigotConfig.restartMessage, true ) );
++ p.connection.disconnect( CraftChatMessage.fromStringOrEmpty( SpigotConfig.restartMessage, true ), org.bukkit.event.player.PlayerKickEvent.Cause.RESTART_COMMAND); // Paper - kick event reason (cause is never used))
+ }
+ // Give the socket a chance to send the packets
+ try
diff --git a/patches/server/0537-Add-PufferFishStateChangeEvent.patch b/patches/server/0537-Add-PufferFishStateChangeEvent.patch
new file mode 100644
index 0000000000..0574c7ed8c
--- /dev/null
+++ b/patches/server/0537-Add-PufferFishStateChangeEvent.patch
@@ -0,0 +1,50 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: HexedHero <[email protected]>
+Date: Mon, 10 May 2021 16:59:05 +0100
+Subject: [PATCH] Add PufferFishStateChangeEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java
+index 6c5fa151867eb2b15b9aaf94eb4f5309c415a92b..cdb74f86ee92ee143af29962a85d45ca585cee44 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java
++++ b/src/main/java/net/minecraft/world/entity/animal/Pufferfish.java
+@@ -102,25 +102,39 @@ public class Pufferfish extends AbstractFish {
+ public void tick() {
+ if (!this.level().isClientSide && this.isAlive() && this.isEffectiveAi()) {
+ if (this.inflateCounter > 0) {
++ boolean increase = true; // Paper - Add PufferFishStateChangeEvent
+ if (this.getPuffState() == 0) {
++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent
+ this.makeSound(SoundEvents.PUFFER_FISH_BLOW_UP);
+ this.setPuffState(1);
++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent
+ } else if (this.inflateCounter > 40 && this.getPuffState() == 1) {
++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 2).callEvent()) { // Paper - Add PufferFishStateChangeEvent
+ this.makeSound(SoundEvents.PUFFER_FISH_BLOW_UP);
+ this.setPuffState(2);
++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent
+ }
+
++ if (increase) { // Paper - Add PufferFishStateChangeEvent
+ ++this.inflateCounter;
++ } // Paper - Add PufferFishStateChangeEvent
+ } else if (this.getPuffState() != 0) {
++ boolean increase = true; // Paper - Add PufferFishStateChangeEvent
+ if (this.deflateTimer > 60 && this.getPuffState() == 2) {
++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 1).callEvent()) { // Paper - Add PufferFishStateChangeEvent
+ this.makeSound(SoundEvents.PUFFER_FISH_BLOW_OUT);
+ this.setPuffState(1);
++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent
+ } else if (this.deflateTimer > 100 && this.getPuffState() == 1) {
++ if (new io.papermc.paper.event.entity.PufferFishStateChangeEvent((org.bukkit.entity.PufferFish) getBukkitEntity(), 0).callEvent()) { // Paper - Add PufferFishStateChangeEvent
+ this.makeSound(SoundEvents.PUFFER_FISH_BLOW_OUT);
+ this.setPuffState(0);
++ } else { increase = false; } // Paper - Add PufferFishStateChangeEvent
+ }
+
++ if (increase) { // Paper - Add PufferFishStateChangeEvent
+ ++this.deflateTimer;
++ } // Paper - Add PufferFishStateChangeEvent
+ }
+ }
+
diff --git a/patches/server/0538-Fix-PlayerBucketEmptyEvent-result-itemstack.patch b/patches/server/0538-Fix-PlayerBucketEmptyEvent-result-itemstack.patch
new file mode 100644
index 0000000000..646e52a326
--- /dev/null
+++ b/patches/server/0538-Fix-PlayerBucketEmptyEvent-result-itemstack.patch
@@ -0,0 +1,42 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 20 May 2021 22:16:37 -0700
+Subject: [PATCH] Fix PlayerBucketEmptyEvent result itemstack
+
+Fixes SPIGOT-2560: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-2560
+
+diff --git a/src/main/java/net/minecraft/world/item/BucketItem.java b/src/main/java/net/minecraft/world/item/BucketItem.java
+index a6aaef9de23bf8084ab13c8f704e9f59de3acdcf..002e2f8e956b2631529e2189be225385dfb501df 100644
+--- a/src/main/java/net/minecraft/world/item/BucketItem.java
++++ b/src/main/java/net/minecraft/world/item/BucketItem.java
+@@ -40,6 +40,8 @@ import org.bukkit.event.player.PlayerBucketFillEvent;
+
+ public class BucketItem extends Item implements DispensibleContainerItem {
+
++ private static @Nullable ItemStack itemLeftInHandAfterPlayerBucketEmptyEvent = null; // Paper - Fix PlayerBucketEmptyEvent result itemstack
++
+ public final Fluid content;
+
+ public BucketItem(Fluid fluid, Item.Properties settings) {
+@@ -125,6 +127,13 @@ public class BucketItem extends Item implements DispensibleContainerItem {
+ }
+
+ public static ItemStack getEmptySuccessItem(ItemStack stack, Player player) {
++ // Paper start - Fix PlayerBucketEmptyEvent result itemstack
++ if (itemLeftInHandAfterPlayerBucketEmptyEvent != null) {
++ ItemStack itemInHand = itemLeftInHandAfterPlayerBucketEmptyEvent;
++ itemLeftInHandAfterPlayerBucketEmptyEvent = null;
++ return itemInHand;
++ }
++ // Paper end - Fix PlayerBucketEmptyEvent result itemstack
+ return !player.hasInfiniteMaterials() ? new ItemStack(Items.BUCKET) : stack;
+ }
+
+@@ -182,6 +191,7 @@ public class BucketItem extends Item implements DispensibleContainerItem {
+ ((ServerPlayer) entityhuman).getBukkitEntity().updateInventory(); // SPIGOT-4541
+ return false;
+ }
++ itemLeftInHandAfterPlayerBucketEmptyEvent = event.getItemStack() != null ? event.getItemStack().equals(CraftItemStack.asNewCraftStack(net.minecraft.world.item.Items.BUCKET)) ? null : CraftItemStack.asNMSCopy(event.getItemStack()) : ItemStack.EMPTY; // Paper - Fix PlayerBucketEmptyEvent result itemstack
+ }
+ // CraftBukkit end
+ if (!flag2) {
diff --git a/patches/server/0539-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch b/patches/server/0539-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch
new file mode 100644
index 0000000000..ee4d412a1c
--- /dev/null
+++ b/patches/server/0539-Synchronize-PalettedContainer-instead-of-ThreadingDe.patch
@@ -0,0 +1,91 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <[email protected]>
+Date: Fri, 29 May 2020 20:29:02 -0400
+Subject: [PATCH] Synchronize PalettedContainer instead of
+ ThreadingDetector/Semaphore
+
+Mojang has flaws in their logic about chunks being concurrently
+wrote to. So we constantly see crashes around multiple threads writing.
+
+Additionally, java has optimized synchronization so well that its
+in many times faster than trying to manage read write locks for low
+contention situations.
+
+And this is extremely a low contention situation.
+
+diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+index 8c318c5f9eaaae1e961ff24247283c232fa84c20..112d1259dd37743076ff6c67ffd711d084ba8698 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
++++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
+@@ -30,14 +30,14 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+ public final IdMap<T> registry;
+ private volatile PalettedContainer.Data<T> data;
+ private final PalettedContainer.Strategy strategy;
+- private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer");
++ // private final ThreadingDetector threadingDetector = new ThreadingDetector("PalettedContainer"); // Paper - unused
+
+ public void acquire() {
+- this.threadingDetector.checkAndLock();
++ // this.threadingDetector.checkAndLock(); // Paper - disable this - use proper synchronization
+ }
+
+ public void release() {
+- this.threadingDetector.checkAndUnlock();
++ // this.threadingDetector.checkAndUnlock(); // Paper - disable this
+ }
+
+ public static <T> Codec<PalettedContainer<T>> codecRW(IdMap<T> idList, Codec<T> entryCodec, PalettedContainer.Strategy paletteProvider, T defaultValue) {
+@@ -110,7 +110,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+ }
+
+ @Override
+- public int onResize(int newBits, T object) {
++ public synchronized int onResize(int newBits, T object) { // Paper - synchronize
+ PalettedContainer.Data<T> data = this.data;
+ PalettedContainer.Data<T> data2 = this.createOrReuseData(data, newBits);
+ data2.copyFrom(data.palette, data.storage);
+@@ -135,7 +135,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+ return this.getAndSet(this.strategy.getIndex(x, y, z), value);
+ }
+
+- private T getAndSet(int index, T value) {
++ private synchronized T getAndSet(int index, T value) { // Paper - synchronize
+ int i = this.data.palette.idFor(value);
+ int j = this.data.storage.getAndSet(index, i);
+ return this.data.palette.valueFor(j);
+@@ -151,7 +151,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+ }
+ }
+
+- private void set(int index, T value) {
++ private synchronized void set(int index, T value) { // Paper - synchronize
+ int i = this.data.palette.idFor(value);
+ this.data.storage.set(index, i);
+ }
+@@ -174,7 +174,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+ intSet.forEach(id -> action.accept(palette.valueFor(id)));
+ }
+
+- public void read(FriendlyByteBuf buf) {
++ public synchronized void read(FriendlyByteBuf buf) { // Paper - synchronize
+ this.acquire();
+
+ try {
+@@ -189,7 +189,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+ }
+
+ @Override
+- public void write(FriendlyByteBuf buf) {
++ public synchronized void write(FriendlyByteBuf buf) { // Paper - synchronize
+ this.acquire();
+
+ try {
+@@ -237,7 +237,7 @@ public class PalettedContainer<T> implements PaletteResize<T>, PalettedContainer
+ }
+
+ @Override
+- public PalettedContainerRO.PackedData<T> pack(IdMap<T> idList, PalettedContainer.Strategy paletteProvider) {
++ public synchronized PalettedContainerRO.PackedData<T> pack(IdMap<T> idList, PalettedContainer.Strategy paletteProvider) { // Paper - synchronize
+ this.acquire();
+
+ PalettedContainerRO.PackedData var12;
diff --git a/patches/server/0540-Add-option-to-fix-items-merging-through-walls.patch b/patches/server/0540-Add-option-to-fix-items-merging-through-walls.patch
new file mode 100644
index 0000000000..b4e4e39ae1
--- /dev/null
+++ b/patches/server/0540-Add-option-to-fix-items-merging-through-walls.patch
@@ -0,0 +1,25 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: GioSDA <[email protected]>
+Date: Wed, 10 Mar 2021 10:06:45 -0800
+Subject: [PATCH] Add option to fix items merging through walls
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
+index 9974aec00935a1c3068eceee6d7042f14f15ac56..586257fe5c9f5cddd0ed164254f46777c6e71d66 100644
+--- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
++++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java
+@@ -285,6 +285,14 @@ public class ItemEntity extends Entity implements TraceableEntity {
+ ItemEntity entityitem = (ItemEntity) iterator.next();
+
+ if (entityitem.isMergable()) {
++ // Paper start - Fix items merging through walls
++ if (this.level().paperConfig().fixes.fixItemsMergingThroughWalls) {
++ if (this.level().clipDirect(this.position(), entityitem.position(),
++ net.minecraft.world.phys.shapes.CollisionContext.of(this)) == net.minecraft.world.phys.HitResult.Type.BLOCK) {
++ continue;
++ }
++ }
++ // Paper end - Fix items merging through walls
+ this.tryToMerge(entityitem);
+ if (this.isRemoved()) {
+ break;
diff --git a/patches/server/0541-Add-BellRevealRaiderEvent.patch b/patches/server/0541-Add-BellRevealRaiderEvent.patch
new file mode 100644
index 0000000000..871aa2a198
--- /dev/null
+++ b/patches/server/0541-Add-BellRevealRaiderEvent.patch
@@ -0,0 +1,33 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Wed, 26 May 2021 17:09:07 -0400
+Subject: [PATCH] Add BellRevealRaiderEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java
+index 86dac3f82da065bf79d94da9df192f51ce4665e2..946c9dbfabf154db53d811906fd98d17992167d1 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/BellBlockEntity.java
+@@ -156,7 +156,7 @@ public class BellBlockEntity extends BlockEntity {
+ return BellBlockEntity.isRaiderWithinRange(pos, entityliving);
+ }).map((entity) -> (org.bukkit.entity.LivingEntity) entity.getBukkitEntity()).collect(java.util.stream.Collectors.toCollection(java.util.ArrayList::new)); // CraftBukkit
+
+- org.bukkit.craftbukkit.event.CraftEventFactory.handleBellResonateEvent(world, pos, entities).forEach(BellBlockEntity::glow);
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBellResonateEvent(world, pos, entities).forEach(entity -> glow(entity, pos)); // Paper - Add BellRevealRaiderEvent
+ // CraftBukkit end
+ }
+
+@@ -189,6 +189,13 @@ public class BellBlockEntity extends BlockEntity {
+ }
+
+ private static void glow(LivingEntity entity) {
++ // Paper start - Add BellRevealRaiderEvent
++ glow(entity, null);
++ }
++
++ private static void glow(LivingEntity entity, @javax.annotation.Nullable BlockPos pos) {
++ if (pos != null && !new io.papermc.paper.event.block.BellRevealRaiderEvent(org.bukkit.craftbukkit.block.CraftBlock.at(entity.level(), pos), (org.bukkit.entity.Raider) entity.getBukkitEntity()).callEvent()) return;
++ // Paper end - Add BellRevealRaiderEvent
+ entity.addEffect(new MobEffectInstance(MobEffects.GLOWING, 60));
+ }
+
diff --git a/patches/server/0542-Fix-invulnerable-end-crystals.patch b/patches/server/0542-Fix-invulnerable-end-crystals.patch
new file mode 100644
index 0000000000..4349e5a8ad
--- /dev/null
+++ b/patches/server/0542-Fix-invulnerable-end-crystals.patch
@@ -0,0 +1,65 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Max Lee <[email protected]>
+Date: Thu, 27 May 2021 14:52:30 -0700
+Subject: [PATCH] Fix invulnerable end crystals
+
+MC-108513
+
+diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
+index 671e8aa7ecc2b3fcc98af62356ff690a2604bcb0..7cb3d69a69e0e3ef4b7f9f9c8b1eb67edb5d116d 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EndCrystal.java
+@@ -30,6 +30,7 @@ public class EndCrystal extends Entity {
+ private static final EntityDataAccessor<Optional<BlockPos>> DATA_BEAM_TARGET = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.OPTIONAL_BLOCK_POS);
+ private static final EntityDataAccessor<Boolean> DATA_SHOW_BOTTOM = SynchedEntityData.defineId(EndCrystal.class, EntityDataSerializers.BOOLEAN);
+ public int time;
++ public boolean generatedByDragonFight = false; // Paper - Fix invulnerable end crystals
+
+ public EndCrystal(EntityType<? extends EndCrystal> type, Level world) {
+ super(type, world);
+@@ -68,6 +69,17 @@ public class EndCrystal extends Entity {
+ }
+ // CraftBukkit end
+ }
++ // Paper start - Fix invulnerable end crystals
++ if (this.level().paperConfig().unsupportedSettings.fixInvulnerableEndCrystalExploit && this.generatedByDragonFight && this.isInvulnerable()) {
++ if (!java.util.Objects.equals(((ServerLevel) this.level()).uuid, this.getOriginWorld())
++ || ((ServerLevel) this.level()).getDragonFight() == null
++ || ((ServerLevel) this.level()).getDragonFight().respawnStage == null
++ || ((ServerLevel) this.level()).getDragonFight().respawnStage.ordinal() > net.minecraft.world.level.dimension.end.DragonRespawnAnimation.SUMMONING_DRAGON.ordinal()) {
++ this.setInvulnerable(false);
++ this.setBeamTarget(null);
++ }
++ }
++ // Paper end - Fix invulnerable end crystals
+ }
+
+ }
+@@ -79,6 +91,7 @@ public class EndCrystal extends Entity {
+ }
+
+ nbt.putBoolean("ShowBottom", this.showsBottom());
++ if (this.generatedByDragonFight) nbt.putBoolean("Paper.GeneratedByDragonFight", this.generatedByDragonFight); // Paper - Fix invulnerable end crystals
+ }
+
+ @Override
+@@ -87,6 +100,7 @@ public class EndCrystal extends Entity {
+ if (nbt.contains("ShowBottom", 1)) {
+ this.setShowBottom(nbt.getBoolean("ShowBottom"));
+ }
++ if (nbt.contains("Paper.GeneratedByDragonFight", 1)) this.generatedByDragonFight = nbt.getBoolean("Paper.GeneratedByDragonFight"); // Paper - Fix invulnerable end crystals
+
+ }
+
+diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java
+index 6f0cd7121bf191d8fd01baf4c9b87df7f4f44564..fe5b2bcfaa243c3089f3df83ec1ae0948a63d1eb 100644
+--- a/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java
++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/SpikeFeature.java
+@@ -115,6 +115,7 @@ public class SpikeFeature extends Feature<SpikeConfiguration> {
+ endCrystal.moveTo(
+ (double)spike.getCenterX() + 0.5, (double)(spike.getHeight() + 1), (double)spike.getCenterZ() + 0.5, random.nextFloat() * 360.0F, 0.0F
+ );
++ endCrystal.generatedByDragonFight = true; // Paper - Fix invulnerable end crystals
+ world.addFreshEntity(endCrystal);
+ BlockPos blockPos2 = endCrystal.blockPosition();
+ this.setBlock(world, blockPos2.below(), Blocks.BEDROCK.defaultBlockState());
diff --git a/patches/server/0543-Add-ElderGuardianAppearanceEvent.patch b/patches/server/0543-Add-ElderGuardianAppearanceEvent.patch
new file mode 100644
index 0000000000..e28248ff5d
--- /dev/null
+++ b/patches/server/0543-Add-ElderGuardianAppearanceEvent.patch
@@ -0,0 +1,48 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Fri, 19 Mar 2021 23:39:09 -0400
+Subject: [PATCH] Add ElderGuardianAppearanceEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/effect/MobEffectUtil.java b/src/main/java/net/minecraft/world/effect/MobEffectUtil.java
+index f8026eb1d9b10e468d28ee2e1296653e873c87db..23977c2074b920b646a639bef79c0f625b284bea 100644
+--- a/src/main/java/net/minecraft/world/effect/MobEffectUtil.java
++++ b/src/main/java/net/minecraft/world/effect/MobEffectUtil.java
+@@ -55,10 +55,23 @@ public final class MobEffectUtil {
+ }
+
+ public static List<ServerPlayer> addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause) {
++ // Paper start - Add ElderGuardianAppearanceEvent
++ return addEffectToPlayersAround(worldserver, entity, vec3d, d0, mobeffect, i, cause, null);
++ }
++
++ public static List<ServerPlayer> addEffectToPlayersAround(ServerLevel worldserver, @Nullable Entity entity, Vec3 vec3d, double d0, MobEffectInstance mobeffect, int i, org.bukkit.event.entity.EntityPotionEffectEvent.Cause cause, @Nullable java.util.function.Predicate<ServerPlayer> playerPredicate) {
++ // Paper end - Add ElderGuardianAppearanceEvent
+ // CraftBukkit end
+ Holder<MobEffect> holder = mobeffect.getEffect();
+ List<ServerPlayer> list = worldserver.getPlayers((entityplayer) -> {
+- return entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(holder) || entityplayer.getEffect(holder).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(holder).endsWithin(i - 1));
++ // Paper start - Add ElderGuardianAppearanceEvent
++ boolean condition = entityplayer.gameMode.isSurvival() && (entity == null || !entity.isAlliedTo((Entity) entityplayer)) && vec3d.closerThan(entityplayer.position(), d0) && (!entityplayer.hasEffect(holder) || entityplayer.getEffect(holder).getAmplifier() < mobeffect.getAmplifier() || entityplayer.getEffect(holder).endsWithin(i - 1));
++ if (condition) {
++ return playerPredicate == null || playerPredicate.test(entityplayer); // Only test the player AFTER it is true
++ } else {
++ return false;
++ }
++ // Paper ned - Add ElderGuardianAppearanceEvent
+ });
+
+ list.forEach((entityplayer) -> {
+diff --git a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java
+index 9290f43b4b37a7fa2afae81f8351ea76b7ee7de0..378694a38115c012978e1fea59d049d1ebd04110 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java
++++ b/src/main/java/net/minecraft/world/entity/monster/ElderGuardian.java
+@@ -67,7 +67,7 @@ public class ElderGuardian extends Guardian {
+ super.customServerAiStep(world);
+ if ((this.tickCount + this.getId()) % 1200 == 0) {
+ MobEffectInstance mobeffect = new MobEffectInstance(MobEffects.DIG_SLOWDOWN, 6000, 2);
+- List<ServerPlayer> list = MobEffectUtil.addEffectToPlayersAround(world, this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK); // CraftBukkit
++ List<ServerPlayer> list = MobEffectUtil.addEffectToPlayersAround(world, this, this.position(), 50.0D, mobeffect, 1200, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.ATTACK, (player) -> new io.papermc.paper.event.entity.ElderGuardianAppearanceEvent((org.bukkit.entity.ElderGuardian) this.getBukkitEntity(), player.getBukkitEntity()).callEvent()); // CraftBukkit // Paper - Add ElderGuardianAppearanceEvent
+
+ list.forEach((entityplayer) -> {
+ entityplayer.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, this.isSilent() ? 0.0F : 1.0F));
diff --git a/patches/server/0544-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch b/patches/server/0544-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch
new file mode 100644
index 0000000000..3d03f4c4d2
--- /dev/null
+++ b/patches/server/0544-Optimize-Biome-Mob-Lookups-for-Mob-Spawning.patch
@@ -0,0 +1,53 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Aikar <[email protected]>
+Date: Wed, 12 Sep 2018 21:47:01 -0400
+Subject: [PATCH] Optimize Biome Mob Lookups for Mob Spawning
+
+Uses an EnumMap as well as a Set paired List for O(1) contains calls.
+
+diff --git a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java
+index d6ebd2ebc3a4a184f1237b00c6c4e709c61d2eec..cb7465ed9bdebe1b31f02d11725e75ff8b44ca66 100644
+--- a/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java
++++ b/src/main/java/net/minecraft/world/level/biome/MobSpawnSettings.java
+@@ -75,8 +75,40 @@ public class MobSpawnSettings {
+ }
+
+ public static class Builder {
++ // Paper start - Perf: keep track of data in a pair set to give O(1) contains calls - we have to hook removals incase plugins mess with it
++ public static class MobList extends java.util.ArrayList<MobSpawnSettings.SpawnerData> {
++ java.util.Set<MobSpawnSettings.SpawnerData> biomes = new java.util.HashSet<>();
++
++ @Override
++ public boolean contains(Object o) {
++ return biomes.contains(o);
++ }
++
++ @Override
++ public boolean add(MobSpawnSettings.SpawnerData BiomeSettingsMobs) {
++ biomes.add(BiomeSettingsMobs);
++ return super.add(BiomeSettingsMobs);
++ }
++
++ @Override
++ public MobSpawnSettings.SpawnerData remove(int index) {
++ MobSpawnSettings.SpawnerData removed = super.remove(index);
++ if (removed != null) {
++ biomes.remove(removed);
++ }
++ return removed;
++ }
++
++ @Override
++ public void clear() {
++ biomes.clear();
++ super.clear();
++ }
++ }
++ // use toImmutableEnumMap collector
+ private final Map<MobCategory, List<MobSpawnSettings.SpawnerData>> spawners = Stream.of(MobCategory.values())
+- .collect(ImmutableMap.toImmutableMap(mobCategory -> (MobCategory)mobCategory, mobCategory -> Lists.newArrayList()));
++ .collect(Maps.toImmutableEnumMap(mobCategory -> (MobCategory)mobCategory, mobCategory -> new MobList())); // Use MobList instead of ArrayList
++ // Paper end - Perf: keep track of data in a pair set to give O(1) contains calls
+ private final Map<EntityType<?>, MobSpawnSettings.MobSpawnCost> mobSpawnCosts = Maps.newLinkedHashMap();
+ private float creatureGenerationProbability = 0.1F;
+
diff --git a/patches/server/0545-Line-Of-Sight-Changes.patch b/patches/server/0545-Line-Of-Sight-Changes.patch
new file mode 100644
index 0000000000..a2ee93fb8f
--- /dev/null
+++ b/patches/server/0545-Line-Of-Sight-Changes.patch
@@ -0,0 +1,74 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: TwoLeggedCat <[email protected]>
+Date: Sat, 29 May 2021 14:33:25 -0500
+Subject: [PATCH] Line Of Sight Changes
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index 509139ab9a3070c8c96d14fdc4d07bc229b41bd6..b7c854614643cc6fcb78ce7ac378f5dbbb8eb305 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -3913,7 +3913,8 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ Vec3 vec3d = new Vec3(this.getX(), this.getEyeY(), this.getZ());
+ Vec3 vec3d1 = new Vec3(entity.getX(), entityY, entity.getZ());
+
+- return vec3d1.distanceTo(vec3d) > 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, shapeType, fluidHandling, this)).getType() == HitResult.Type.MISS;
++ // Paper - diff on change - used in CraftLivingEntity#hasLineOfSight(Location) and CraftWorld#lineOfSightExists
++ return vec3d1.distanceToSqr(vec3d) > 128.0D * 128.0D ? false : this.level().clip(new ClipContext(vec3d, vec3d1, shapeType, fluidHandling, this)).getType() == HitResult.Type.MISS; // Paper - Perf: Use distance squared
+ }
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java
+index a070b2a83edaa702b13bc6d3026914126c211576..ca35e93239eea09b3d0dc6ef18f58743e633996b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java
+@@ -526,5 +526,21 @@ public abstract class CraftRegionAccessor implements RegionAccessor {
+ public org.bukkit.NamespacedKey getKey() {
+ return org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(this.getHandle().getLevel().dimension().location());
+ }
++
++ public boolean lineOfSightExists(Location from, Location to) {
++ Preconditions.checkArgument(from != null, "from parameter in lineOfSightExists cannot be null");
++ Preconditions.checkArgument(to != null, "to parameter in lineOfSightExists cannot be null");
++ if (from.getWorld() != to.getWorld()) {
++ return false;
++ }
++
++ net.minecraft.world.phys.Vec3 start = new net.minecraft.world.phys.Vec3(from.getX(), from.getY(), from.getZ());
++ net.minecraft.world.phys.Vec3 end = new net.minecraft.world.phys.Vec3(to.getX(), to.getY(), to.getZ());
++ if (end.distanceToSqr(start) > 128D * 128D) {
++ return false; // Return early if the distance is greater than 128 blocks
++ }
++
++ return this.getHandle().clip(new net.minecraft.world.level.ClipContext(start, end, net.minecraft.world.level.ClipContext.Block.COLLIDER, net.minecraft.world.level.ClipContext.Fluid.NONE, net.minecraft.world.phys.shapes.CollisionContext.empty())).getType() == net.minecraft.world.phys.HitResult.Type.MISS;
++ }
+ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+index a4be1eea356ba99358d707381df70032ded42140..a4a30f52f45bf91dc88d53f5d6a376ed739d09eb 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+@@ -643,6 +643,23 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+ return this.getHandle().hasLineOfSight(((CraftEntity) other).getHandle());
+ }
+
++ // Paper start
++ @Override
++ public boolean hasLineOfSight(Location loc) {
++ if (this.getHandle().level() != ((CraftWorld) loc.getWorld()).getHandle()) {
++ return false;
++ }
++
++ net.minecraft.world.phys.Vec3 start = new net.minecraft.world.phys.Vec3(this.getHandle().getX(), this.getHandle().getEyeY(), this.getHandle().getZ());
++ net.minecraft.world.phys.Vec3 end = new net.minecraft.world.phys.Vec3(loc.getX(), loc.getY(), loc.getZ());
++ if (end.distanceToSqr(start) > 128D * 128D) {
++ return false; // Return early if the distance is greater than 128 blocks
++ }
++
++ return this.getHandle().level().clipDirect(start, end, net.minecraft.world.phys.shapes.CollisionContext.of(this.getHandle())) == net.minecraft.world.phys.HitResult.Type.MISS;
++ }
++ // Paper end
++
+ @Override
+ public boolean getRemoveWhenFarAway() {
+ return this.getHandle() instanceof Mob && !((Mob) this.getHandle()).isPersistenceRequired();
diff --git a/patches/server/0546-add-per-world-spawn-limits.patch b/patches/server/0546-add-per-world-spawn-limits.patch
new file mode 100644
index 0000000000..6ed4ae68af
--- /dev/null
+++ b/patches/server/0546-add-per-world-spawn-limits.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: chase <[email protected]>
+Date: Wed, 2 Dec 2020 22:43:39 -0800
+Subject: [PATCH] add per world spawn limits
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index 691d65bca79d503a43f696d92a0a32226cddaad5..d72ceb866f8632a8daa7dc19acdc57b6b78fd906 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -219,6 +219,13 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+ this.biomeProvider = biomeProvider;
+
+ this.environment = env;
++ // Paper start - per world spawn limits
++ for (SpawnCategory spawnCategory : SpawnCategory.values()) {
++ if (CraftSpawnCategory.isValidForLimits(spawnCategory)) {
++ setSpawnLimit(spawnCategory, this.world.paperConfig().entities.spawning.spawnLimits.getInt(CraftSpawnCategory.toNMS(spawnCategory)));
++ }
++ }
++ // Paper end - per world spawn limits
+ }
+
+ @Override
diff --git a/patches/server/0547-Fix-potions-splash-events.patch b/patches/server/0547-Fix-potions-splash-events.patch
new file mode 100644
index 0000000000..68350d52f2
--- /dev/null
+++ b/patches/server/0547-Fix-potions-splash-events.patch
@@ -0,0 +1,181 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 20 May 2021 20:40:53 -0700
+Subject: [PATCH] Fix potions splash events
+
+Fix PotionSplashEvent for water splash potions
+Fixes SPIGOT-6221: https://hub.spigotmc.org/jira/projects/SPIGOT/issues/SPIGOT-6221
+Fix splash events cancellation that still show particles/sound
+
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
+index 224e768963d6969f25608065d37ad72d82acda68..d6ac07d9d5ee0430a1d91b7084b378aac1d047e5 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
+@@ -109,55 +109,76 @@ public class ThrownPotion extends ThrowableItemProjectile {
+ ItemStack itemstack = this.getItem();
+ PotionContents potioncontents = (PotionContents) itemstack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);
+
++ boolean showParticles = true; // Paper - Fix potions splash events
+ if (potioncontents.is(Potions.WATER)) {
+- this.applyWater(worldserver);
++ showParticles = this.applyWater(worldserver, hitResult); // Paper - Fix potions splash events
+ } else if (true || potioncontents.hasEffects()) { // CraftBukkit - Call event even if no effects to apply
+ if (this.isLingering()) {
+- this.makeAreaOfEffectCloud(potioncontents, hitResult); // CraftBukkit - Pass MovingObjectPosition
++ showParticles = this.makeAreaOfEffectCloud(potioncontents, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper
+ } else {
+- this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition
++ showParticles = this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper
+ }
+ }
+
++ if (showParticles) { // Paper - Fix potions splash events
+ int i = potioncontents.potion().isPresent() && ((Potion) ((Holder) potioncontents.potion().get()).value()).hasInstantEffects() ? 2007 : 2002;
+
+ worldserver.levelEvent(i, this.blockPosition(), potioncontents.getColor());
++ } // Paper - Fix potions splash events
+ this.discard(EntityRemoveEvent.Cause.HIT); // CraftBukkit - add Bukkit remove cause
+ }
+ }
+
+- private void applyWater(ServerLevel world) {
++ private static final Predicate<net.minecraft.world.entity.LivingEntity> APPLY_WATER_GET_ENTITIES_PREDICATE = ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE.or(Axolotl.class::isInstance); // Paper - Fix potions splash events
++ private boolean applyWater(ServerLevel world, @Nullable HitResult hitResult) { // Paper - Fix potions splash events
+ AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D);
+- List<net.minecraft.world.entity.LivingEntity> list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.WATER_SENSITIVE_OR_ON_FIRE);
++ // Paper start - Fix potions splash events
++ List<net.minecraft.world.entity.LivingEntity> list = this.level().getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb, ThrownPotion.APPLY_WATER_GET_ENTITIES_PREDICATE);
++ Map<LivingEntity, Double> affected = new HashMap<>();
++ java.util.Set<LivingEntity> rehydrate = new java.util.HashSet<>();
++ java.util.Set<LivingEntity> extinguish = new java.util.HashSet<>();
+ Iterator iterator = list.iterator();
+
+ while (iterator.hasNext()) {
+ net.minecraft.world.entity.LivingEntity entityliving = (net.minecraft.world.entity.LivingEntity) iterator.next();
++ if (entityliving instanceof Axolotl axolotl) {
++ rehydrate.add(((org.bukkit.entity.Axolotl) axolotl.getBukkitEntity()));
++ }
+ double d0 = this.distanceToSqr((Entity) entityliving);
+
+ if (d0 < 16.0D) {
+ if (entityliving.isSensitiveToWater()) {
+- entityliving.hurtServer(world, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F);
++ affected.put(entityliving.getBukkitLivingEntity(), 1.0);
+ }
+
+ if (entityliving.isOnFire() && entityliving.isAlive()) {
+- entityliving.extinguishFire();
++ extinguish.add(entityliving.getBukkitLivingEntity());
+ }
+ }
+ }
+
+- List<Axolotl> list1 = this.level().getEntitiesOfClass(Axolotl.class, axisalignedbb);
+- Iterator iterator1 = list1.iterator();
+-
+- while (iterator1.hasNext()) {
+- Axolotl axolotl = (Axolotl) iterator1.next();
+-
+- axolotl.rehydrate();
++ io.papermc.paper.event.entity.WaterBottleSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callWaterBottleSplashEvent(
++ this, hitResult, affected, rehydrate, extinguish
++ );
++ if (!event.isCancelled()) {
++ for (LivingEntity affectedEntity : event.getToDamage()) {
++ ((CraftLivingEntity) affectedEntity).getHandle().hurtServer(world, this.damageSources().indirectMagic(this, this.getOwner()), 1.0F);
++ }
++ for (LivingEntity toExtinguish : event.getToExtinguish()) {
++ ((CraftLivingEntity) toExtinguish).getHandle().extinguishFire();
++ }
++ for (LivingEntity toRehydrate : event.getToRehydrate()) {
++ if (((CraftLivingEntity) toRehydrate).getHandle() instanceof Axolotl axolotl) {
++ axolotl.rehydrate();
++ }
++ }
++ // Paper end - Fix potions splash events
+ }
++ return !event.isCancelled(); // Paper - Fix potions splash events
+
+ }
+
+- private void applySplash(ServerLevel worldserver, Iterable<MobEffectInstance> iterable, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition
++ private boolean applySplash(ServerLevel worldserver, Iterable<MobEffectInstance> iterable, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events
+ AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D);
+ List<net.minecraft.world.entity.LivingEntity> list = worldserver.getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb);
+ Map<LivingEntity, Double> affected = new HashMap<LivingEntity, Double>(); // CraftBukkit
+@@ -175,6 +196,7 @@ public class ThrownPotion extends ThrowableItemProjectile {
+ if (d0 < 16.0D) {
+ double d1;
+
++ // Paper - diff on change, used when calling the splash event for water splash potions
+ if (entityliving == entity) {
+ d1 = 1.0D;
+ } else {
+@@ -230,10 +252,11 @@ public class ThrownPotion extends ThrowableItemProjectile {
+ }
+ }
+ }
++ return !event.isCancelled(); // Paper - Fix potions splash events
+
+ }
+
+- private void makeAreaOfEffectCloud(PotionContents potioncontents, HitResult position) { // CraftBukkit - Pass MovingObjectPosition
++ private boolean makeAreaOfEffectCloud(PotionContents potioncontents, HitResult position) { // CraftBukkit - Pass MovingObjectPosition
+ AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
+ Entity entity = this.getOwner();
+
+@@ -246,14 +269,16 @@ public class ThrownPotion extends ThrowableItemProjectile {
+ entityareaeffectcloud.setWaitTime(10);
+ entityareaeffectcloud.setRadiusPerTick(-entityareaeffectcloud.getRadius() / (float) entityareaeffectcloud.getDuration());
+ entityareaeffectcloud.setPotionContents(potioncontents);
++ boolean noEffects = potioncontents.hasEffects(); // Paper - Fix potions splash events
+ // CraftBukkit start
+ org.bukkit.event.entity.LingeringPotionSplashEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callLingeringPotionSplashEvent(this, position, entityareaeffectcloud);
+- if (!(event.isCancelled() || entityareaeffectcloud.isRemoved())) {
++ if (!(event.isCancelled() || entityareaeffectcloud.isRemoved() || (noEffects && !entityareaeffectcloud.potionContents.hasEffects()))) { // Paper - don't spawn area effect cloud if the effects were empty and not changed during the event handling
+ this.level().addFreshEntity(entityareaeffectcloud);
+ } else {
+ entityareaeffectcloud.discard(null); // CraftBukkit - add Bukkit remove cause
+ }
+ // CraftBukkit end
++ return !event.isCancelled(); // Paper - Fix potions splash events
+ }
+
+ public boolean isLingering() {
+diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+index 49efae40345fcfca8f80fcc541dcfde1b8a8b07b..24f86724012bb8bcd6d24683a1c78ce66b42ca30 100644
+--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+@@ -884,6 +884,32 @@ public class CraftEventFactory {
+ return event;
+ }
+
++ // Paper start - Fix potions splash events
++ public static io.papermc.paper.event.entity.WaterBottleSplashEvent callWaterBottleSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult hitResult, Map<LivingEntity, Double> affectedEntities, java.util.Set<LivingEntity> rehydrate, java.util.Set<LivingEntity> extinguish) {
++ ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity();
++
++ Block hitBlock = null;
++ BlockFace hitFace = null;
++ org.bukkit.entity.Entity hitEntity = null;
++
++ if (hitResult != null) {
++ if (hitResult.getType() == HitResult.Type.BLOCK) {
++ BlockHitResult blockHitResult = (BlockHitResult) hitResult;
++ hitBlock = CraftBlock.at(potion.level(), blockHitResult.getBlockPos());
++ hitFace = CraftBlock.notchToBlockFace(blockHitResult.getDirection());
++ } else if (hitResult.getType() == HitResult.Type.ENTITY) {
++ hitEntity = ((EntityHitResult) hitResult).getEntity().getBukkitEntity();
++ }
++ }
++
++ io.papermc.paper.event.entity.WaterBottleSplashEvent event = new io.papermc.paper.event.entity.WaterBottleSplashEvent(
++ thrownPotion, hitEntity, hitBlock, hitFace, affectedEntities, rehydrate, extinguish
++ );
++ event.callEvent();
++ return event;
++ }
++ // Paper end - Fix potions splash events
++
+ /**
+ * BlockFadeEvent
+ */
diff --git a/patches/server/0548-Add-more-LimitedRegion-API.patch b/patches/server/0548-Add-more-LimitedRegion-API.patch
new file mode 100644
index 0000000000..f46a2383c8
--- /dev/null
+++ b/patches/server/0548-Add-more-LimitedRegion-API.patch
@@ -0,0 +1,56 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: dfsek <[email protected]>
+Date: Sat, 19 Jun 2021 20:15:59 -0700
+Subject: [PATCH] Add more LimitedRegion API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java
+index ca250dba422f8092b99f72b322147061a7541d14..2c7b64bd7071cb803a042152d497982d753e0b5d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java
++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java
+@@ -255,4 +255,45 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe
+ public void addEntityWithPassengers(net.minecraft.world.entity.Entity entity, CreatureSpawnEvent.SpawnReason reason) {
+ this.entities.add(entity);
+ }
++
++ // Paper start - Add more LimitedRegion API
++ @Override
++ public void setBlockState(int x, int y, int z, BlockState state) {
++ BlockPos pos = new BlockPos(x, y, z);
++ if (!state.getBlockData().matches(getHandle().getBlockState(pos).createCraftBlockData())) {
++ throw new IllegalArgumentException("BlockData does not match! Expected " + state.getBlockData().getAsString(false) + ", got " + getHandle().getBlockState(pos).createCraftBlockData().getAsString(false));
++ }
++ getHandle().getBlockEntity(pos).loadWithComponents(((org.bukkit.craftbukkit.block.CraftBlockEntityState<?>) state).getSnapshotNBT(), this.getHandle().registryAccess());
++ }
++
++ @Override
++ public void scheduleBlockUpdate(int x, int y, int z) {
++ BlockPos position = new BlockPos(x, y, z);
++ getHandle().scheduleTick(position, getHandle().getBlockState(position).getBlock(), 0);
++ }
++
++ @Override
++ public void scheduleFluidUpdate(int x, int y, int z) {
++ BlockPos position = new BlockPos(x, y, z);
++ getHandle().scheduleTick(position, getHandle().getFluidState(position).getType(), 0);
++ }
++
++ @Override
++ public World getWorld() {
++ // reading/writing the returned Minecraft world causes a deadlock.
++ // By implementing this, and covering it in warnings, we're assuming people won't be stupid, and
++ // if they are stupid, they'll figure it out pretty fast.
++ return getHandle().getMinecraftWorld().getWorld();
++ }
++
++ @Override
++ public int getCenterChunkX() {
++ return centerChunkX;
++ }
++
++ @Override
++ public int getCenterChunkZ() {
++ return centerChunkZ;
++ }
++ // Paper end - Add more LimitedRegion API
+ }
diff --git a/patches/server/0549-Fix-PlayerDropItemEvent-using-wrong-item.patch b/patches/server/0549-Fix-PlayerDropItemEvent-using-wrong-item.patch
new file mode 100644
index 0000000000..6fbcb401ee
--- /dev/null
+++ b/patches/server/0549-Fix-PlayerDropItemEvent-using-wrong-item.patch
@@ -0,0 +1,57 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Sun, 20 Jun 2021 21:55:59 -0700
+Subject: [PATCH] Fix PlayerDropItemEvent using wrong item
+
+
+diff --git a/src/main/java/net/minecraft/server/commands/GiveCommand.java b/src/main/java/net/minecraft/server/commands/GiveCommand.java
+index c81fd3e1108fb0a02f9240263404af2b968c8494..0d9de4c61c7b26a6ff37c12fde629161fd0c3d5a 100644
+--- a/src/main/java/net/minecraft/server/commands/GiveCommand.java
++++ b/src/main/java/net/minecraft/server/commands/GiveCommand.java
+@@ -38,6 +38,7 @@ public class GiveCommand {
+
+ private static int giveItem(CommandSourceStack source, ItemInput item, Collection<ServerPlayer> targets, int count) throws CommandSyntaxException {
+ ItemStack itemstack = item.createItemStack(1, false);
++ final Component displayName = itemstack.getDisplayName(); // Paper - get display name early
+ int j = itemstack.getMaxStackSize();
+ int k = j * 100;
+
+@@ -79,11 +80,11 @@ public class GiveCommand {
+
+ if (targets.size() == 1) {
+ source.sendSuccess(() -> {
+- return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), ((ServerPlayer) targets.iterator().next()).getDisplayName());
++ return Component.translatable("commands.give.success.single", count, displayName, ((ServerPlayer) targets.iterator().next()).getDisplayName()); // Paper - use cached display name
+ }, true);
+ } else {
+ source.sendSuccess(() -> {
+- return Component.translatable("commands.give.success.single", count, itemstack.getDisplayName(), targets.size());
++ return Component.translatable("commands.give.success.single", count, displayName, targets.size()); // Paper - use cached display name
+ }, true);
+ }
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index 2125f9dc87dbf31a66004dc859788f584e099e73..b1224b0ef12a746477047073f5bb94405871ce85 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -2724,7 +2724,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+
+ if (flag1) {
+ if (!itemstack1.isEmpty()) {
+- this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack.getCount());
++ this.awardStat(Stats.ITEM_DROPPED.get(itemstack1.getItem()), itemstack1.getCount()); // Paper - Fix PlayerDropItemEvent using wrong item
+ }
+
+ this.awardStat(Stats.DROP);
+@@ -2740,6 +2740,11 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ return null;
+ } else {
+ double d0 = this.getEyeY() - 0.30000001192092896D;
++ // Paper start
++ ItemStack tmp = stack.copy();
++ stack.setCount(0);
++ stack = tmp;
++ // Paper end
+ ItemEntity entityitem = new ItemEntity(this.level(), this.getX(), d0, this.getZ(), stack);
+
+ entityitem.setPickUpDelay(40);
diff --git a/patches/server/0550-Missing-Entity-API.patch b/patches/server/0550-Missing-Entity-API.patch
new file mode 100644
index 0000000000..3f4ceb1648
--- /dev/null
+++ b/patches/server/0550-Missing-Entity-API.patch
@@ -0,0 +1,1402 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Mon, 21 Jun 2021 23:56:07 -0400
+Subject: [PATCH] Missing Entity API
+
+== AT ==
+public net.minecraft.world.entity.animal.Fox isDefending()Z
+public net.minecraft.world.entity.animal.Fox setDefending(Z)V
+public net.minecraft.world.entity.animal.Fox setFaceplanted(Z)V
+public net.minecraft.world.entity.animal.Panda getEatCounter()I
+public net.minecraft.world.entity.animal.Panda setEatCounter(I)V
+public net.minecraft.world.entity.animal.Bee isRolling()Z
+public net.minecraft.world.entity.animal.Bee setRolling(Z)V
+public net.minecraft.world.entity.animal.Bee numCropsGrownSincePollination
+public net.minecraft.world.entity.animal.Bee ticksWithoutNectarSinceExitingHive
+public net.minecraft.world.entity.monster.piglin.Piglin isChargingCrossbow()Z
+public net.minecraft.world.entity.ambient.Bat targetPosition
+public net.minecraft.world.entity.monster.Ravager attackTick
+public net.minecraft.world.entity.monster.Ravager stunnedTick
+public net.minecraft.world.entity.monster.Ravager roarTick
+public net.minecraft.world.entity.vehicle.MinecartTNT explode(D)V
+public net.minecraft.world.entity.vehicle.MinecartTNT fuse
+public net.minecraft.world.entity.monster.Endermite life
+public net.minecraft.world.entity.projectile.AbstractArrow soundEvent
+public net.minecraft.world.entity.monster.Phantom anchorPoint
+public net.minecraft.world.entity.npc.WanderingTrader getWanderTarget()Lnet/minecraft/core/BlockPos;
+public net.minecraft.world.entity.animal.AbstractSchoolingFish leader
+public net.minecraft.world.entity.animal.AbstractSchoolingFish schoolSize
+public net.minecraft.world.entity.animal.Rabbit moreCarrotTicks
+public net.minecraft.world.entity.AreaEffectCloud ownerUUID
+public net.minecraft.world.entity.animal.MushroomCow stewEffects
+public net.minecraft.world.entity.Entity FLAG_INVISIBLE
+public net.minecraft.world.entity.animal.Cat setRelaxStateOne(Z)V
+public net.minecraft.world.entity.animal.Cat isRelaxStateOne()Z
+
+Co-authored-by: Nassim Jahnke <[email protected]>
+Co-authored-by: Jake Potrebic <[email protected]>
+Co-authored-by: William Blake Galbreath <[email protected]>
+Co-authored-by: SoSeDiK <[email protected]>
+Co-authored-by: booky10 <[email protected]>
+Co-authored-by: Amin <[email protected]>
+Co-authored-by: TrollyLoki <[email protected]>
+Co-authored-by: FireInstall <[email protected]>
+Co-authored-by: maxcom1 <[email protected]>
+Co-authored-by: TotalledZebra <[email protected]>
+
+diff --git a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
+index 05e793e722bbb367ca64cd7f26156fa3dabb8474..3470720466fc81f977c18e3a97bb918926025a22 100644
+--- a/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
++++ b/src/main/java/com/destroystokyo/paper/entity/ai/MobGoalHelper.java
+@@ -164,7 +164,7 @@ public class MobGoalHelper {
+ bukkitMap.put(net.minecraft.world.entity.monster.Endermite.class, Endermite.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Evoker.class, Evoker.class);
+ bukkitMap.put(AbstractFish.class, Fish.class);
+- bukkitMap.put(AbstractSchoolingFish.class, Fish.class); // close enough
++ bukkitMap.put(AbstractSchoolingFish.class, io.papermc.paper.entity.SchoolableFish.class);
+ bukkitMap.put(FlyingMob.class, Flying.class);
+ bukkitMap.put(net.minecraft.world.entity.animal.Fox.class, Fox.class);
+ bukkitMap.put(net.minecraft.world.entity.monster.Ghast.class, Ghast.class);
+diff --git a/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..41bf71d116ffc5431586ce54abba7f8def6c1dcf
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/entity/PaperSchoolableFish.java
+@@ -0,0 +1,52 @@
++package io.papermc.paper.entity;
++
++import net.minecraft.world.entity.animal.AbstractSchoolingFish;
++import org.bukkit.craftbukkit.CraftServer;
++import org.bukkit.craftbukkit.entity.CraftFish;
++import org.jetbrains.annotations.NotNull;
++
++public class PaperSchoolableFish extends CraftFish implements SchoolableFish {
++
++ public PaperSchoolableFish(CraftServer server, AbstractSchoolingFish entity) {
++ super(server, entity);
++ }
++
++ @Override
++ public AbstractSchoolingFish getHandle() {
++ return (AbstractSchoolingFish) super.getHandle();
++ }
++
++ @Override
++ public void startFollowing(@NotNull SchoolableFish fish) {
++ if (this.getHandle().isFollower()) { // If following a fish already, properly remove the old one
++ this.stopFollowing();
++ }
++
++ this.getHandle().startFollowing(((PaperSchoolableFish) fish).getHandle());
++ }
++
++ @Override
++ public void stopFollowing() {
++ this.getHandle().stopFollowing();
++ }
++
++ @Override
++ public int getSchoolSize() {
++ return this.getHandle().schoolSize;
++ }
++
++ @Override
++ public int getMaxSchoolSize() {
++ return this.getHandle().getMaxSchoolSize();
++ }
++
++ @Override
++ public SchoolableFish getSchoolLeader() {
++ AbstractSchoolingFish leader = this.getHandle().leader;
++ if (leader == null) {
++ return null;
++ }
++
++ return (SchoolableFish) leader.getBukkitEntity();
++ }
++}
+diff --git a/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java b/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java
+index 30095df7b64cfda4931dbfa22549ff5abefd53e0..c8ae49f58c254119c0e64a4e1501ebc5a70f9a46 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java
++++ b/src/main/java/net/minecraft/world/entity/animal/AbstractSchoolingFish.java
+@@ -51,6 +51,7 @@ public abstract class AbstractSchoolingFish extends AbstractFish {
+ }
+
+ public void stopFollowing() {
++ if (this.leader == null) return; // Avoid NPE, plugins can now set the leader and certain fish goals might cause this method to be called
+ this.leader.removeFollower();
+ this.leader = null;
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java
+index a48a29b5b1963db679b053f3530f64d2b9560290..a5eab5cdb761e3e9d37ea66287f26a2c3345182d 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/Bee.java
++++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java
+@@ -565,11 +565,13 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal {
+ this.setFlag(4, hasStung);
+ }
+
++ public net.kyori.adventure.util.TriState rollingOverride = net.kyori.adventure.util.TriState.NOT_SET; // Paper - Rolling override
+ public boolean isRolling() {
+ return this.getFlag(2);
+ }
+
+ public void setRolling(boolean nearTarget) {
++ nearTarget = rollingOverride.toBooleanOrElse(nearTarget); // Paper - Rolling override
+ this.setFlag(2, nearTarget);
+ }
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
+index b9d7aa4aa0fc3472a2a0700e526778daf62bdc6f..48ac8c3f6e00c3c2dc67b6c994be7c0ac6dfcf81 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
++++ b/src/main/java/net/minecraft/world/entity/animal/frog/Tadpole.java
+@@ -50,6 +50,7 @@ public class Tadpole extends AbstractFish {
+ public int age;
+ protected static final ImmutableList<SensorType<? extends Sensor<? super Tadpole>>> SENSOR_TYPES = ImmutableList.of(SensorType.NEAREST_LIVING_ENTITIES, SensorType.NEAREST_PLAYERS, SensorType.HURT_BY, SensorType.FROG_TEMPTATIONS);
+ protected static final ImmutableList<MemoryModuleType<?>> MEMORY_TYPES = ImmutableList.of(MemoryModuleType.LOOK_TARGET, MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, MemoryModuleType.WALK_TARGET, MemoryModuleType.CANT_REACH_WALK_TARGET_SINCE, MemoryModuleType.PATH, MemoryModuleType.NEAREST_VISIBLE_ADULT, MemoryModuleType.TEMPTATION_COOLDOWN_TICKS, MemoryModuleType.IS_TEMPTED, MemoryModuleType.TEMPTING_PLAYER, MemoryModuleType.BREED_TARGET, MemoryModuleType.IS_PANICKING);
++ public boolean ageLocked; // Paper
+
+ public Tadpole(EntityType<? extends AbstractFish> type, Level world) {
+ super(type, world);
+@@ -102,7 +103,7 @@ public class Tadpole extends AbstractFish {
+ @Override
+ public void aiStep() {
+ super.aiStep();
+- if (!this.level().isClientSide) {
++ if (!this.level().isClientSide && !this.ageLocked) { // Paper
+ this.setAge(this.age + 1);
+ }
+
+@@ -112,12 +113,14 @@ public class Tadpole extends AbstractFish {
+ public void addAdditionalSaveData(CompoundTag nbt) {
+ super.addAdditionalSaveData(nbt);
+ nbt.putInt("Age", this.age);
++ nbt.putBoolean("AgeLocked", this.ageLocked); // Paper
+ }
+
+ @Override
+ public void readAdditionalSaveData(CompoundTag nbt) {
+ super.readAdditionalSaveData(nbt);
+ this.setAge(nbt.getInt("Age"));
++ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper
+ }
+
+ @Nullable
+@@ -169,6 +172,7 @@ public class Tadpole extends AbstractFish {
+ Bucketable.saveDefaultDataToBucketTag(this, stack);
+ CustomData.update(DataComponents.BUCKET_ENTITY_DATA, stack, (nbttagcompound) -> {
+ nbttagcompound.putInt("Age", this.getAge());
++ nbttagcompound.putBoolean("AgeLocked", this.ageLocked); // Paper
+ });
+ }
+
+@@ -179,6 +183,7 @@ public class Tadpole extends AbstractFish {
+ this.setAge(nbt.getInt("Age"));
+ }
+
++ this.ageLocked = nbt.getBoolean("AgeLocked"); // Paper
+ }
+
+ @Override
+@@ -210,6 +215,7 @@ public class Tadpole extends AbstractFish {
+ }
+
+ private void ageUp(int seconds) {
++ if (this.ageLocked) return; // Paper
+ this.setAge(this.age + seconds * 20);
+ }
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
+index 709237639ef0ec1cb623f270302a27b0072e8685..74151d69380e4adede40c7d7fc20834553706730 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
++++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java
+@@ -779,6 +779,15 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
+
+ }
+
++ // Paper start - Horse API
++ public void setMouthOpen(boolean open) {
++ this.setFlag(FLAG_OPEN_MOUTH, open);
++ }
++ public boolean isMouthOpen() {
++ return this.getFlag(FLAG_OPEN_MOUTH);
++ }
++ // Paper end - Horse API
++
+ @Override
+ public InteractionResult mobInteract(Player player, InteractionHand hand) {
+ if (!this.isVehicle() && !this.isBaby()) {
+@@ -821,6 +830,11 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
+ this.setFlag(16, eatingGrass);
+ }
+
++ // Paper start - Horse API
++ public void setForceStanding(boolean standing) {
++ this.setFlag(FLAG_STANDING, standing);
++ }
++ // Paper end - Horse API
+ public void setStanding(boolean angry) {
+ if (angry) {
+ this.setEating(false);
+diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
+index 04842dd7b9beabcecbd492d0b98faaebeea1a5d9..d5808d0c190877554a4a8191f68e8b4a36f2ff46 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
++++ b/src/main/java/net/minecraft/world/entity/animal/horse/Llama.java
+@@ -71,11 +71,12 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
+ @Nullable
+ private Llama caravanHead;
+ @Nullable
+- private Llama caravanTail;
++ public Llama caravanTail; // Paper
+
+ public Llama(EntityType<? extends Llama> type, Level world) {
+ super(type, world);
+ this.getNavigation().setRequiredPathLength(40.0F);
++ this.maxDomestication = 30; // Paper - Missing entity API; configure max temper instead of a hardcoded value
+ }
+
+ public boolean isTraderLlama() {
+@@ -294,7 +295,7 @@ public class Llama extends AbstractChestedHorse implements VariantHolder<Llama.V
+
+ @Override
+ public int getMaxTemper() {
+- return 30;
++ return super.getMaxTemper(); // Paper - Missing entity API; delegate to parent
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
+index af721305a3b31f4aa9a36dfbc1cbe0cd278fa6ad..8415f7e1f8c391961dc5b0669da1ab4f8ae4950d 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
+@@ -86,6 +86,11 @@ public class WitherBoss extends Monster implements RangedAttackMob {
+ return !entityliving.getType().is(EntityTypeTags.WITHER_FRIENDS) && entityliving.attackable();
+ };
+ private static final TargetingConditions TARGETING_CONDITIONS = TargetingConditions.forCombat().range(20.0D).selector(WitherBoss.LIVING_ENTITY_SELECTOR);
++ // Paper start
++ private boolean canPortal = false;
++
++ public void setCanTravelThroughPortals(boolean canPortal) { this.canPortal = canPortal; }
++ // Paper end
+
+ public WitherBoss(EntityType<? extends WitherBoss> type, Level world) {
+ super(type, world);
+@@ -591,7 +596,7 @@ public class WitherBoss extends Monster implements RangedAttackMob {
+
+ @Override
+ public boolean canUsePortal(boolean allowVehicles) {
+- return false;
++ return this.canPortal; // Paper
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
+index 0ddbebbe6eea00c379f2a250f7a44ba9313c1de1..4b798695af365dc97cbbbd09f370b8fc425f9ed6 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
+@@ -428,6 +428,16 @@ public class EnderMan extends Monster implements NeutralMob {
+ this.entityData.set(EnderMan.DATA_STARED_AT, true);
+ }
+
++ // Paper start
++ public void setCreepy(boolean creepy) {
++ this.entityData.set(EnderMan.DATA_CREEPY, creepy);
++ }
++
++ public void setHasBeenStaredAt(boolean hasBeenStaredAt) {
++ this.entityData.set(EnderMan.DATA_STARED_AT, hasBeenStaredAt);
++ }
++ // Paper end
++
+ @Override
+ public boolean requiresCustomPersistence() {
+ return super.requiresCustomPersistence() || this.getCarriedBlock() != null;
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Ghast.java b/src/main/java/net/minecraft/world/entity/monster/Ghast.java
+index 71259b92a01a4feca270a250b1964f25f6da2d33..a8c8c03e972aa6352843cf4c3e4aebfb8f493125 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Ghast.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Ghast.java
+@@ -66,6 +66,12 @@ public class Ghast extends FlyingMob implements Enemy {
+ return this.explosionPower;
+ }
+
++ // Paper start
++ public void setExplosionPower(int explosionPower) {
++ this.explosionPower = explosionPower;
++ }
++ // Paper end
++
+ @Override
+ protected boolean shouldDespawnInPeaceful() {
+ return true;
+diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
+index 7f4c186a757e51ac788ec664a2c75dc7d7ce0eb3..a5d13b8bf7d0b6423ef428042e1134f5999cc24b 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
++++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java
+@@ -207,6 +207,12 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder {
+ }
+
+ public void startConverting(@Nullable UUID uuid, int delay) {
++ // Paper start - missing entity behaviour api - converting without entity event
++ this.startConverting(uuid, delay, true);
++ }
++
++ public void startConverting(@Nullable UUID uuid, int delay, boolean broadcastEntityEvent) {
++ // Paper end - missing entity behaviour api - converting without entity event
+ this.conversionStarter = uuid;
+ this.villagerConversionTime = delay;
+ this.getEntityData().set(ZombieVillager.DATA_CONVERTING_ID, true);
+@@ -214,7 +220,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder {
+ this.removeEffect(MobEffects.WEAKNESS, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION);
+ this.addEffect(new MobEffectInstance(MobEffects.DAMAGE_BOOST, delay, Math.min(this.level().getDifficulty().getId() - 1, 0)), org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION);
+ // CraftBukkit end
+- this.level().broadcastEntityEvent(this, (byte) 16);
++ if (broadcastEntityEvent) this.level().broadcastEntityEvent(this, (byte) 16); // Paper - missing entity behaviour api - converting without entity event
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java
+index 20a9dae93ee17bdef16b67a2db8e5e78b94d2c53..3888f13a49af26b0ece8813b607c20fc380cecd5 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownTrident.java
+@@ -114,6 +114,20 @@ public class ThrownTrident extends AbstractArrow {
+ return (Boolean) this.entityData.get(ThrownTrident.ID_FOIL);
+ }
+
++ // Paper start
++ public void setFoil(boolean foil) {
++ this.entityData.set(ThrownTrident.ID_FOIL, foil);
++ }
++
++ public int getLoyalty() {
++ return this.entityData.get(ThrownTrident.ID_LOYALTY);
++ }
++
++ public void setLoyalty(byte loyalty) {
++ this.entityData.set(ThrownTrident.ID_LOYALTY, loyalty);
++ }
++ // Paper end
++
+ @Nullable
+ @Override
+ protected EntityHitResult findHitEntity(Vec3 currentPosition, Vec3 nextPosition) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java
+index 3952e52b94c1cc97e1d2d3885f59d7690efb74ad..9bcc0931510607b8fbd01233e2b3c346369b214d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractHorse.java
+@@ -114,4 +114,36 @@ public abstract class CraftAbstractHorse extends CraftAnimals implements Abstrac
+ public AbstractHorseInventory getInventory() {
+ return new CraftSaddledInventory(getHandle().inventory);
+ }
++
++ // Paper start - Horse API
++ @Override
++ public boolean isEatingGrass() {
++ return this.getHandle().isEating();
++ }
++
++ @Override
++ public void setEatingGrass(boolean eating) {
++ this.getHandle().setEating(eating);
++ }
++
++ @Override
++ public boolean isRearing() {
++ return this.getHandle().isStanding();
++ }
++
++ @Override
++ public void setRearing(boolean rearing) {
++ this.getHandle().setForceStanding(rearing);
++ }
++
++ @Override
++ public boolean isEating() {
++ return this.getHandle().isMouthOpen();
++ }
++
++ @Override
++ public void setEating(boolean eating) {
++ this.getHandle().setMouthOpen(eating);
++ }
++ // Paper end - Horse API
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java
+index 17bffb45453f15328ca91794e26f6be1defef700..6591513bb62226b6f85fd2ef9f6ebe376f0f7362 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java
+@@ -229,4 +229,17 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud
+ this.getHandle().setOwner(null);
+ }
+ }
++
++ // Paper start - owner API
++ @Override
++ public java.util.UUID getOwnerUniqueId() {
++ return this.getHandle().ownerUUID;
++ }
++
++ @Override
++ public void setOwnerUniqueId(final java.util.UUID ownerUuid) {
++ this.getHandle().setOwner(null);
++ this.getHandle().ownerUUID = ownerUuid;
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java
+index b0a3531476f5a05ae846b68d825eddc35ebddea9..1bb72f28085f3885bec068b586ec222111044884 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBat.java
+@@ -27,4 +27,25 @@ public class CraftBat extends CraftAmbient implements Bat {
+ public void setAwake(boolean state) {
+ this.getHandle().setResting(!state);
+ }
++ // Paper start
++ @Override
++ public org.bukkit.Location getTargetLocation() {
++ net.minecraft.core.BlockPos pos = this.getHandle().targetPosition;
++ if (pos == null) {
++ return null;
++ }
++
++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos);
++ }
++
++ @Override
++ public void setTargetLocation(org.bukkit.Location location) {
++ net.minecraft.core.BlockPos pos = null;
++ if (location != null) {
++ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location);
++ }
++
++ this.getHandle().targetPosition = pos;
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java
+index cfff1be6a4a4936a2dadb2590abc3d33c123d048..3dac93b0ab5d5acf5b33dc4b0efed60319eb657b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftBee.java
+@@ -86,4 +86,42 @@ public class CraftBee extends CraftAnimals implements Bee {
+ public void setCannotEnterHiveTicks(int ticks) {
+ this.getHandle().setStayOutOfHiveCountdown(ticks);
+ }
++ // Paper start
++ @Override
++ public void setRollingOverride(net.kyori.adventure.util.TriState rolling) {
++ this.getHandle().rollingOverride = rolling;
++
++ this.getHandle().setRolling(this.getHandle().isRolling()); // Refresh rolling state
++ }
++
++ @Override
++ public boolean isRolling() {
++ return this.getRollingOverride().toBooleanOrElse(this.getHandle().isRolling());
++ }
++
++ @Override
++ public net.kyori.adventure.util.TriState getRollingOverride() {
++ return this.getHandle().rollingOverride;
++ }
++
++ @Override
++ public void setCropsGrownSincePollination(int crops) {
++ this.getHandle().numCropsGrownSincePollination = crops;
++ }
++
++ @Override
++ public int getCropsGrownSincePollination() {
++ return this.getHandle().numCropsGrownSincePollination;
++ }
++
++ @Override
++ public void setTicksSincePollination(int ticks) {
++ this.getHandle().ticksWithoutNectarSinceExitingHive = ticks;
++ }
++
++ @Override
++ public int getTicksSincePollination() {
++ return this.getHandle().ticksWithoutNectarSinceExitingHive;
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java
+index 57664124968e6268ad6699c6bd932981cc2fe6ba..88e876da7df64b68a5b71fd1deab75b59c5a64e3 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java
+@@ -139,4 +139,26 @@ public class CraftCat extends CraftTameableAnimal implements Cat {
+ return this.getKey().hashCode();
+ }
+ }
++
++ // Paper start - More cat api
++ @Override
++ public void setLyingDown(boolean lyingDown) {
++ this.getHandle().setLying(lyingDown);
++ }
++
++ @Override
++ public boolean isLyingDown() {
++ return this.getHandle().isLying();
++ }
++
++ @Override
++ public void setHeadUp(boolean headUp) {
++ this.getHandle().setRelaxStateOne(headUp);
++ }
++
++ @Override
++ public boolean isHeadUp() {
++ return this.getHandle().isRelaxStateOne();
++ }
++ // Paper end - More cat api
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java
+index 64b75682a936e071353707f7615d6ff512fd617d..96f6e2fd9c6b20d34122abfe5c7fba732502d5a0 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java
+@@ -18,4 +18,26 @@ public class CraftChicken extends CraftAnimals implements Chicken {
+ public String toString() {
+ return "CraftChicken";
+ }
++
++ // Paper start
++ @Override
++ public boolean isChickenJockey() {
++ return this.getHandle().isChickenJockey();
++ }
++
++ @Override
++ public void setIsChickenJockey(boolean isChickenJockey) {
++ this.getHandle().setChickenJockey(isChickenJockey);
++ }
++
++ @Override
++ public int getEggLayTime() {
++ return this.getHandle().eggTime;
++ }
++
++ @Override
++ public void setEggLayTime(int eggLayTime) {
++ this.getHandle().eggTime = eggLayTime;
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java
+index fa0bf7db880063427ba12df1df1c72240fff93e9..63e6b07e3b159c74d9ef17be20b5ab43d07f0f5f 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftCod.java
+@@ -3,7 +3,7 @@ package org.bukkit.craftbukkit.entity;
+ import org.bukkit.craftbukkit.CraftServer;
+ import org.bukkit.entity.Cod;
+
+-public class CraftCod extends CraftFish implements Cod {
++public class CraftCod extends io.papermc.paper.entity.PaperSchoolableFish implements Cod { // Paper - School Fish API
+
+ public CraftCod(CraftServer server, net.minecraft.world.entity.animal.Cod entity) {
+ super(server, entity);
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java
+index 5bae70ad160d0d0912aa9ef054c5515812957289..83867b9c5497e6e793b21c482646cc419587e182 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftDolphin.java
+@@ -18,4 +18,36 @@ public class CraftDolphin extends CraftAgeable implements Dolphin {
+ public String toString() {
+ return "CraftDolphin";
+ }
++
++ // Paper start - Missing Dolphin API
++ @Override
++ public int getMoistness() {
++ return this.getHandle().getMoistnessLevel();
++ }
++
++ @Override
++ public void setMoistness(int moistness) {
++ this.getHandle().setMoisntessLevel(moistness);
++ }
++
++ @Override
++ public void setHasFish(boolean hasFish) {
++ this.getHandle().setGotFish(hasFish);
++ }
++
++ @Override
++ public boolean hasFish() {
++ return this.getHandle().gotFish();
++ }
++
++ @Override
++ public org.bukkit.Location getTreasureLocation() {
++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), this.getHandle().getTreasurePos());
++ }
++
++ @Override
++ public void setTreasureLocation(org.bukkit.Location location) {
++ this.getHandle().setTreasurePos(io.papermc.paper.util.MCUtil.toBlockPosition(location));
++ }
++ // Paper end - Missing Dolphin API
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java
+index cc4194ac9d7501b5d15655674dade14d59cb6733..33ae03b78b01c005a291a343b42507fb539e81a6 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragonPart.java
+@@ -51,6 +51,13 @@ public class CraftEnderDragonPart extends CraftComplexPart implements EnderDrago
+ this.getParent().setHealth(health);
+ }
+
++ // Paper start - entity heal API
++ @Override
++ public void heal(final double amount, final org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason reason) {
++ this.getParent().heal(amount, reason);
++ }
++ // Paper end - entity heal API
++
+ @Override
+ public double getAbsorptionAmount() {
+ return this.getParent().getAbsorptionAmount();
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java
+index 21dc209e6f98b6306833b41e2763e746047d5a94..983b9d6ddb58eff297e96e5c8b28ec427efa267d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderman.java
+@@ -40,6 +40,28 @@ public class CraftEnderman extends CraftMonster implements Enderman {
+ this.getHandle().setCarriedBlock(blockData == null ? null : ((CraftBlockData) blockData).getState());
+ }
+
++ // Paper start
++ @Override
++ public boolean isScreaming() {
++ return this.getHandle().isCreepy();
++ }
++
++ @Override
++ public void setScreaming(boolean screaming) {
++ this.getHandle().setCreepy(screaming);
++ }
++
++ @Override
++ public boolean hasBeenStaredAt() {
++ return this.getHandle().hasBeenStaredAt();
++ }
++
++ @Override
++ public void setHasBeenStaredAt(boolean hasBeenStaredAt) {
++ this.getHandle().setHasBeenStaredAt(hasBeenStaredAt);
++ }
++ // Paper end
++
+ @Override
+ public EnderMan getHandle() {
+ return (EnderMan) this.entity;
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java
+index fc0f0e841dc974d080e1abb9bbafb5165801131f..d657fd2c507a5b215aeab0a5f3e9c2ee892a27c8 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEndermite.java
+@@ -28,4 +28,15 @@ public class CraftEndermite extends CraftMonster implements Endermite {
+ public void setPlayerSpawned(boolean playerSpawned) {
+ // Nop
+ }
++ // Paper start
++ @Override
++ public void setLifetimeTicks(int ticks) {
++ this.getHandle().life = ticks;
++ }
++
++ @Override
++ public int getLifetimeTicks() {
++ return this.getHandle().life;
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+index 78afac72265e3f586c0203951b8237832fb7c6fb..888a75423ac90ca85308eeb6d67bac5348bf31e0 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+@@ -1082,4 +1082,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
+ return set;
+ }
+ // Paper end - tracked players API
++
++ // Paper start - missing entity api
++ @Override
++ public boolean isInvisible() { // Paper - moved up from LivingEntity
++ return this.getHandle().isInvisible();
++ }
++
++ @Override
++ public void setInvisible(boolean invisible) { // Paper - moved up from LivingEntity
++ this.getHandle().persistentInvisibility = invisible;
++ this.getHandle().setSharedFlag(Entity.FLAG_INVISIBLE, invisible);
++ }
++
++ @Override
++ public void setNoPhysics(boolean noPhysics) {
++ this.getHandle().noPhysics = noPhysics;
++ }
++
++ @Override
++ public boolean hasNoPhysics() {
++ return this.getHandle().noPhysics;
++ }
++ // Paper end - missing entity api
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java
+index 142f3e3257afebb2e831fd0970678123d99a1717..1b084d63bdbb24dad45d28eed1693eb6e26e24dc 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java
+@@ -84,6 +84,18 @@ public class CraftFireball extends AbstractProjectile implements Fireball {
+ return new Vector(delta.x, delta.y, delta.z);
+ }
+
++ // Paper start - Expose power on fireball projectiles
++ @Override
++ public void setPower(final Vector power) {
++ this.setAcceleration(power);
++ }
++
++ @Override
++ public Vector getPower() {
++ return this.getAcceleration();
++ }
++ // Paper end - Expose power on fireball projectiles
++
+ @Override
+ public AbstractHurtingProjectile getHandle() {
+ return (AbstractHurtingProjectile) this.entity;
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java
+index dd912be2933864236cd4fb35631d505972082d77..bb2b59ce9775a0d1dd9828885e57c14cf40d9f04 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFox.java
+@@ -113,4 +113,41 @@ public class CraftFox extends CraftAnimals implements Fox {
+ public boolean isFaceplanted() {
+ return this.getHandle().isFaceplanted();
+ }
++
++ // Paper start - Add more fox behavior API
++ @Override
++ public void setInterested(boolean interested) {
++ this.getHandle().setIsInterested(interested);
++ }
++
++ @Override
++ public boolean isInterested() {
++ return this.getHandle().isInterested();
++ }
++
++ @Override
++ public void setLeaping(boolean leaping) {
++ this.getHandle().setIsPouncing(leaping);
++ }
++
++ @Override
++ public boolean isLeaping() {
++ return this.getHandle().isPouncing();
++ }
++
++ @Override
++ public void setDefending(boolean defending) {
++ this.getHandle().setDefending(defending);
++ }
++
++ @Override
++ public boolean isDefending() {
++ return this.getHandle().isDefending();
++ }
++
++ @Override
++ public void setFaceplanted(boolean faceplanted) {
++ this.getHandle().setFaceplanted(faceplanted);
++ }
++ // Paper end - Add more fox behavior API
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java
+index 2cec61a1bb050c1ef81c5fc3d0afafe9ff29d459..97fa4e1e70203194bd939618b2fad92665af6d59 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGhast.java
+@@ -28,4 +28,17 @@ public class CraftGhast extends CraftFlying implements Ghast, CraftEnemy {
+ public void setCharging(boolean flag) {
+ this.getHandle().setCharging(flag);
+ }
++
++ // Paper start
++ @Override
++ public int getExplosionPower() {
++ return this.getHandle().getExplosionPower();
++ }
++
++ @Override
++ public void setExplosionPower(int explosionPower) {
++ com.google.common.base.Preconditions.checkArgument(explosionPower >= 0 && explosionPower <= 127, "The explosion power has to be between 0 and 127");
++ this.getHandle().setExplosionPower(explosionPower);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+index a4a30f52f45bf91dc88d53f5d6a376ed739d09eb..6020c0c164595db4e2001edf0c1fbe99ed87682d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+@@ -128,6 +128,13 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+ }
+ }
+
++ // Paper start - entity heal API
++ @Override
++ public void heal(final double amount, final org.bukkit.event.entity.EntityRegainHealthEvent.RegainReason reason) {
++ this.getHandle().heal((float) amount, reason);
++ }
++ // Paper end - entity heal API
++
+ @Override
+ public double getAbsorptionAmount() {
+ return this.getHandle().getAbsorptionAmount();
+@@ -939,14 +946,29 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+
+ @Override
+ public boolean isInvisible() {
+- return this.getHandle().isInvisible();
++ return super.isInvisible(); // Paper - move invisibility up to Entity - diff on change
+ }
+
+ @Override
+ public void setInvisible(boolean invisible) {
+- this.getHandle().persistentInvisibility = invisible;
+- this.getHandle().setSharedFlag(5, invisible);
++ super.setInvisible(invisible); // Paper - move invisibility up to Entity
+ }
++ // Paper start
++ @Override
++ public float getSidewaysMovement() {
++ return this.getHandle().xxa;
++ }
++
++ @Override
++ public float getForwardsMovement() {
++ return this.getHandle().zza;
++ }
++
++ @Override
++ public float getUpwardsMovement() {
++ return this.getHandle().yya;
++ }
++ // Paper end
+
+ // Paper start
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
+index bf297388c75521266c93580a9caafe6bad70ab45..351f42842b780d053cd2e5bad9ae299449141b10 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlama.java
+@@ -58,4 +58,36 @@ public class CraftLlama extends CraftChestedHorse implements Llama, com.destroys
+ public String toString() {
+ return "CraftLlama";
+ }
++
++ // Paper start
++ @Override
++ public boolean inCaravan() {
++ return this.getHandle().inCaravan();
++ }
++
++ @Override
++ public void joinCaravan(@org.jetbrains.annotations.NotNull Llama llama) {
++ this.getHandle().joinCaravan(((CraftLlama) llama).getHandle());
++ }
++
++ @Override
++ public void leaveCaravan() {
++ this.getHandle().leaveCaravan();
++ }
++
++ @Override
++ public boolean hasCaravanTail() {
++ return this.getHandle().hasCaravanTail();
++ }
++
++ @Override
++ public Llama getCaravanHead() {
++ return this.getHandle().getCaravanHead() == null ? null : (Llama) this.getHandle().getCaravanHead().getBukkitEntity();
++ }
++
++ @Override
++ public Llama getCaravanTail() {
++ return this.getHandle().caravanTail == null ? null : (Llama) this.getHandle().caravanTail.getBukkitEntity();
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
+index 17f5684cba9d3ed22d9925d1951520cc4751dfe2..3a3563a1bdbc0d84d973b3a04b50b78b4bc3d379 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartHopper.java
+@@ -33,4 +33,20 @@ public final class CraftMinecartHopper extends CraftMinecartContainer implements
+ public void setEnabled(boolean enabled) {
+ ((MinecartHopper) this.getHandle()).setEnabled(enabled);
+ }
++ // Paper start
++ @Override
++ public net.minecraft.world.entity.vehicle.MinecartHopper getHandle() {
++ return (net.minecraft.world.entity.vehicle.MinecartHopper) super.getHandle();
++ }
++
++ @Override
++ public int getPickupCooldown() {
++ throw new UnsupportedOperationException("Hopper minecarts don't have cooldowns");
++ }
++
++ @Override
++ public void setPickupCooldown(int cooldown) {
++ throw new UnsupportedOperationException("Hopper minecarts don't have cooldowns");
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
+index bd739428a7e5e35ebcdb70cd187379b3d222339b..7cf42f62d91c131b1cab576979f85c58c3cecb3b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
+@@ -146,4 +146,16 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob {
+ return getHandle().getMaxHeadXRot();
+ }
+ // Paper end
++
++ // Paper start
++ @Override
++ public boolean isAggressive() {
++ return this.getHandle().isAggressive();
++ }
++
++ @Override
++ public void setAggressive(boolean aggressive) {
++ this.getHandle().setAggressive(aggressive);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java
+index 5467e4a74b70ff57b49d9e6bc686c493178f8511..01d104d91de9e1319d27e39d3f474318c7809486 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPanda.java
+@@ -41,6 +41,38 @@ public class CraftPanda extends CraftAnimals implements Panda {
+ this.getHandle().setHiddenGene(CraftPanda.toNms(gene));
+ }
+
++ // Paper start - Panda API
++ @Override
++ public void setSneezeTicks(int ticks) {
++ this.getHandle().setSneezeCounter(ticks);
++ }
++
++ @Override
++ public int getSneezeTicks() {
++ return this.getHandle().getSneezeCounter();
++ }
++
++ @Override
++ public void setEatingTicks(int ticks) {
++ this.getHandle().setEatCounter(ticks);
++ }
++
++ @Override
++ public int getEatingTicks() {
++ return this.getHandle().getEatCounter();
++ }
++
++ @Override
++ public void setUnhappyTicks(int ticks) {
++ this.getHandle().setUnhappyCounter(ticks);
++ }
++
++ @Override
++ public Gene getCombinedGene() {
++ return CraftPanda.fromNms(this.getHandle().getVariant());
++ }
++ // Paper end - Panda API
++
+ @Override
+ public boolean isRolling() {
+ return this.getHandle().isRolling();
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java
+index 9304e201db1ec96d0916aa8ea781f3e4bc7991e6..83e77c6d287d8e239d2f55f3e9f19ef74946be7c 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPhantom.java
+@@ -44,5 +44,17 @@ public class CraftPhantom extends CraftFlying implements Phantom, CraftEnemy {
+ public void setShouldBurnInDay(boolean shouldBurnInDay) {
+ getHandle().setShouldBurnInDay(shouldBurnInDay);
+ }
++
++ @Override
++ public org.bukkit.Location getAnchorLocation() {
++ net.minecraft.core.BlockPos pos = this.getHandle().anchorPoint;
++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos);
++ }
++
++ @Override
++ public void setAnchorLocation(org.bukkit.Location location) {
++ com.google.common.base.Preconditions.checkArgument(location != null, "location cannot be null");
++ this.getHandle().anchorPoint = io.papermc.paper.util.MCUtil.toBlockPosition(location);
++ }
+ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java
+index f5ecb8c1dc92e5a4b123effd2859123b17a586d3..5124a383b60b2c8de89fa992547d0c61db760c21 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPiglin.java
+@@ -84,4 +84,37 @@ public class CraftPiglin extends CraftPiglinAbstract implements Piglin, com.dest
+ public String toString() {
+ return "CraftPiglin";
+ }
++ // Paper start
++ @Override
++ public void setChargingCrossbow(boolean chargingCrossbow) {
++ this.getHandle().setChargingCrossbow(chargingCrossbow);
++ }
++
++ @Override
++ public boolean isChargingCrossbow() {
++ return this.getHandle().isChargingCrossbow();
++ }
++
++ @Override
++ public void setDancing(boolean dancing) {
++ if (dancing) {
++ this.getHandle().getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING, true);
++ this.getHandle().getBrain().setMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION, this.getHandle().getOnPos());
++ } else {
++ this.getHandle().getBrain().eraseMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING);
++ this.getHandle().getBrain().eraseMemory(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION);
++ }
++ }
++
++ @Override
++ public void setDancing(long duration) {
++ this.getHandle().getBrain().setMemoryWithExpiry(net.minecraft.world.entity.ai.memory.MemoryModuleType.DANCING, true, duration);
++ this.getHandle().getBrain().setMemoryWithExpiry(net.minecraft.world.entity.ai.memory.MemoryModuleType.CELEBRATE_LOCATION, this.getHandle().getOnPos(), duration);
++ }
++
++ @Override
++ public boolean isDancing() {
++ return this.getHandle().isDancing();
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java
+index c7aec6f28e5d3546235b30f6b1112440a76163c5..fe075cfdf3097d6cb768e71b8cc360abb8eaf367 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPolarBear.java
+@@ -17,4 +17,16 @@ public class CraftPolarBear extends CraftAnimals implements PolarBear {
+ public String toString() {
+ return "CraftPolarBear";
+ }
++
++ // Paper start
++ @Override
++ public boolean isStanding() {
++ return this.getHandle().isStanding();
++ }
++
++ @Override
++ public void setStanding(boolean standing) {
++ this.getHandle().setStanding(standing);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java
+index 6b48b117a9cba12aae055c0ea981dfb5bc03a86e..519ef701a7d6534f7cb516f6296b95ee521f661d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRabbit.java
+@@ -29,4 +29,15 @@ public class CraftRabbit extends CraftAnimals implements Rabbit {
+ public void setRabbitType(Type type) {
+ this.getHandle().setVariant(net.minecraft.world.entity.animal.Rabbit.Variant.values()[type.ordinal()]);
+ }
++ // Paper start
++ @Override
++ public void setMoreCarrotTicks(int ticks) {
++ this.getHandle().moreCarrotTicks = ticks;
++ }
++
++ @Override
++ public int getMoreCarrotTicks() {
++ return this.getHandle().moreCarrotTicks;
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java
+index cae59f77c704a5b9515dc4917ed5fdc89631ecfb..09796ce15658e3f7c223a265a547a51ee729ed40 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftRavager.java
+@@ -18,4 +18,35 @@ public class CraftRavager extends CraftRaider implements Ravager {
+ public String toString() {
+ return "CraftRavager";
+ }
++ // Paper start - Missing Entity Behavior
++ @Override
++ public int getAttackTicks() {
++ return this.getHandle().getAttackTick();
++ }
++
++ @Override
++ public void setAttackTicks(int ticks) {
++ this.getHandle().attackTick = ticks;
++ }
++
++ @Override
++ public int getStunnedTicks() {
++ return this.getHandle().getStunnedTick();
++ }
++
++ @Override
++ public void setStunnedTicks(int ticks) {
++ this.getHandle().stunnedTick = ticks;
++ }
++
++ @Override
++ public int getRoarTicks() {
++ return this.getHandle().getRoarTick();
++ }
++
++ @Override
++ public void setRoarTicks(int ticks) {
++ this.getHandle().roarTick = ticks;
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java
+index 551c30cb0fb41dfe4a03663d34ecf9764566c215..7660cc21e936002ebb23510f0ec2b58d71e5157d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSalmon.java
+@@ -4,7 +4,7 @@ import com.google.common.base.Preconditions;
+ import org.bukkit.craftbukkit.CraftServer;
+ import org.bukkit.entity.Salmon;
+
+-public class CraftSalmon extends CraftFish implements Salmon {
++public class CraftSalmon extends io.papermc.paper.entity.PaperSchoolableFish implements Salmon { // Paper - Schooling Fish API
+
+ public CraftSalmon(CraftServer server, net.minecraft.world.entity.animal.Salmon entity) {
+ super(server, entity);
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java
+index 36ab282e2c4060bdea4e57f3ab9dfef9f6cd622c..a61aec087fa7cec27a803668bdc1b9e6eb336755 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTNTPrimed.java
+@@ -67,4 +67,17 @@ public class CraftTNTPrimed extends CraftEntity implements TNTPrimed {
+ this.getHandle().owner = null;
+ }
+ }
++
++ // Paper start
++ @Override
++ public void setBlockData(org.bukkit.block.data.BlockData data) {
++ com.google.common.base.Preconditions.checkArgument(data != null, "The visual block data of this tnt cannot be null. To reset it just set to the TNT default block data");
++ this.getHandle().setBlockState(((org.bukkit.craftbukkit.block.data.CraftBlockData) data).getState());
++ }
++
++ @Override
++ public org.bukkit.block.data.BlockData getBlockData() {
++ return org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(this.getHandle().getBlockState());
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java
+index 451a9bfd9b9b6945e224f1bb05c7951ed934b4e3..d7c6a0bbc5671ea8f2488230c94df5146a1e98b9 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTadpole.java
+@@ -28,4 +28,15 @@ public class CraftTadpole extends CraftFish implements org.bukkit.entity.Tadpole
+ public void setAge(int age) {
+ this.getHandle().age = age;
+ }
++ // Paper start
++ @Override
++ public void setAgeLock(boolean lock) {
++ this.getHandle().ageLocked = lock;
++ }
++
++ @Override
++ public boolean getAgeLock() {
++ return this.getHandle().ageLocked;
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java
+index 33d6e8121755ad6cddacb4fc69e795f9831c27bd..e374b9f40eddca13b30855d25a2030f8df98138f 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java
+@@ -31,4 +31,27 @@ public class CraftTrident extends CraftAbstractArrow implements Trident {
+ public String toString() {
+ return "CraftTrident";
+ }
++
++ // Paper start
++ @Override
++ public boolean hasGlint() {
++ return this.getHandle().isFoil();
++ }
++
++ @Override
++ public void setGlint(boolean glint) {
++ this.getHandle().setFoil(glint);
++ }
++
++ @Override
++ public int getLoyaltyLevel() {
++ return this.getHandle().getLoyalty();
++ }
++
++ @Override
++ public void setLoyaltyLevel(int loyaltyLevel) {
++ com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127");
++ this.getHandle().setLoyalty((byte) loyaltyLevel);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java
+index e3bde6d1c0e03407af1382a61748470063bb2e18..9e53c30801c700719c78c0fd521fd615c94e02c8 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTropicalFish.java
+@@ -7,7 +7,7 @@ import org.bukkit.craftbukkit.CraftServer;
+ import org.bukkit.entity.TropicalFish;
+ import org.bukkit.entity.TropicalFish.Pattern;
+
+-public class CraftTropicalFish extends CraftFish implements TropicalFish {
++public class CraftTropicalFish extends io.papermc.paper.entity.PaperSchoolableFish implements TropicalFish { // Paper - Schooling Fish API
+
+ public CraftTropicalFish(CraftServer server, net.minecraft.world.entity.animal.TropicalFish entity) {
+ super(server, entity);
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java
+index 1cfbe9c476f4a254edf3edf4b70696bbaba78558..e9ec3455eabc473e104b5342a615a38c1ac25a4f 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVex.java
+@@ -29,6 +29,26 @@ public class CraftVex extends CraftMonster implements Vex {
+ public void setSummoner(org.bukkit.entity.Mob summoner) {
+ getHandle().setOwner(summoner == null ? null : ((CraftMob) summoner).getHandle());
+ }
++
++ @Override
++ public boolean hasLimitedLifetime() {
++ return this.getHandle().hasLimitedLife;
++ }
++
++ @Override
++ public void setLimitedLifetime(boolean hasLimitedLifetime) {
++ this.getHandle().hasLimitedLife = hasLimitedLifetime;
++ }
++
++ @Override
++ public int getLimitedLifetimeTicks() {
++ return this.getHandle().limitedLifeTicks;
++ }
++
++ @Override
++ public void setLimitedLifetimeTicks(int ticks) {
++ this.getHandle().limitedLifeTicks = ticks;
++ }
+ // Paper end
+
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java
+index e2a0c11867abee6add8775259c54f2052de7b1ad..3aa23d9f22d5cd22231293fd7d1ca4cb79eb7cb3 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillagerZombie.java
+@@ -60,13 +60,20 @@ public class CraftVillagerZombie extends CraftZombie implements ZombieVillager {
+
+ @Override
+ public void setConversionTime(int time) {
++ // Paper start - missing entity behaviour api - converting without entity event
++ this.setConversionTime(time, true);
++ }
++
++ @Override
++ public void setConversionTime(int time, boolean broadcastEntityEvent) {
++ // Paper end - missing entity behaviour api - converting without entity event
+ if (time < 0) {
+ this.getHandle().villagerConversionTime = -1;
+ this.getHandle().getEntityData().set(net.minecraft.world.entity.monster.ZombieVillager.DATA_CONVERTING_ID, false);
+ this.getHandle().conversionStarter = null;
+ this.getHandle().removeEffect(MobEffects.DAMAGE_BOOST, org.bukkit.event.entity.EntityPotionEffectEvent.Cause.CONVERSION);
+ } else {
+- this.getHandle().startConverting(null, time);
++ this.getHandle().startConverting(null, time, broadcastEntityEvent); // Paper - missing entity behaviour api - converting without entity event
+ }
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java
+index 0e597394a3dd08f022614fc9777302fea581eb55..3cceefa0d6278924a19641a49bdf16bcdacb2233 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWanderingTrader.java
+@@ -49,5 +49,25 @@ public class CraftWanderingTrader extends CraftAbstractVillager implements Wande
+ public boolean canDrinkMilk() {
+ return getHandle().canDrinkMilk;
+ }
++
++ @Override
++ public org.bukkit.Location getWanderingTowards() {
++ net.minecraft.core.BlockPos pos = this.getHandle().getWanderTarget();
++ if (pos == null) {
++ return null;
++ }
++
++ return io.papermc.paper.util.MCUtil.toLocation(this.getHandle().level(), pos);
++ }
++
++ @Override
++ public void setWanderingTowards(org.bukkit.Location location) {
++ net.minecraft.core.BlockPos pos = null;
++ if (location != null) {
++ pos = io.papermc.paper.util.MCUtil.toBlockPosition(location);
++ }
++
++ this.getHandle().setWanderTarget(pos);
++ }
+ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java
+index 794e4fe0a3fbd967f665b2707865c15491370c76..c284eb96a1e330078076cbe61f0f6e2ff4ed89bd 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWarden.java
+@@ -37,6 +37,13 @@ public class CraftWarden extends CraftMonster implements org.bukkit.entity.Warde
+ return this.getHandle().getAngerManagement().getActiveAnger(((CraftEntity) entity).getHandle());
+ }
+
++ // Paper start
++ @Override
++ public int getHighestAnger() {
++ return this.getHandle().getAngerManagement().getActiveAnger(null);
++ }
++ // Paper end
++
+ @Override
+ public void increaseAnger(Entity entity, int increase) {
+ Preconditions.checkArgument(entity != null, "Entity cannot be null");
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java
+index 1113533d281ed159bb735040fb1f913482debf3a..7881c6253c1d652c0c0d54a9a8accdf0a1ff0f3e 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftWither.java
+@@ -67,4 +67,36 @@ public class CraftWither extends CraftMonster implements Wither, com.destroystok
+
+ this.getHandle().setInvulnerableTicks(ticks);
+ }
++
++ // Paper start
++ @Override
++ public boolean isCharged() {
++ return getHandle().isPowered();
++ }
++
++ @Override
++ public int getInvulnerableTicks() {
++ return getHandle().getInvulnerableTicks();
++ }
++
++ @Override
++ public void setInvulnerableTicks(int ticks) {
++ getHandle().setInvulnerableTicks(ticks);
++ }
++
++ @Override
++ public boolean canTravelThroughPortals() {
++ return getHandle().canUsePortal(false);
++ }
++
++ @Override
++ public void setCanTravelThroughPortals(boolean value) {
++ getHandle().setCanTravelThroughPortals(value);
++ }
++
++ @Override
++ public void enterInvulnerabilityPhase() {
++ this.getHandle().makeInvulnerable();
++ }
++ // Paper end
+ }
diff --git a/patches/server/0551-Fix-return-value-of-Block-applyBoneMeal-always-being.patch b/patches/server/0551-Fix-return-value-of-Block-applyBoneMeal-always-being.patch
new file mode 100644
index 0000000000..40cc161932
--- /dev/null
+++ b/patches/server/0551-Fix-return-value-of-Block-applyBoneMeal-always-being.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Mon, 28 Jun 2021 18:16:52 -0700
+Subject: [PATCH] Fix return value of Block#applyBoneMeal always being false
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
+index 54fb380a6896731a18c0100722d12099e590cbc9..9c8aac69f01db647e20d49d272ccc107a7edceaf 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
+@@ -558,7 +558,7 @@ public class CraftBlock implements Block {
+ }
+ }
+
+- return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled());
++ return result == InteractionResult.CONSUME && (event == null || !event.isCancelled()); // Paper - CONSUME is returned on success server-side (see BoneMealItem.applyBoneMeal and InteractionResult.sidedSuccess(boolean))
+ }
+
+ @Override
diff --git a/patches/server/0552-Use-getChunkIfLoadedImmediately-in-places.patch b/patches/server/0552-Use-getChunkIfLoadedImmediately-in-places.patch
new file mode 100644
index 0000000000..3d73d4e80f
--- /dev/null
+++ b/patches/server/0552-Use-getChunkIfLoadedImmediately-in-places.patch
@@ -0,0 +1,53 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Mon, 8 Jul 2019 00:13:36 -0700
+Subject: [PATCH] Use getChunkIfLoadedImmediately in places
+
+This prevents us from hitting chunk loads for chunks at or less-than
+ticket level 33 (yes getChunkIfLoaded will actually perform a chunk
+load in that case).
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index c05ccab70bd81d536f93352f30aab9b07b90876a..93656336af194994f59072fa89bfc338d89d76af 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -233,7 +233,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ public boolean hasEntityMoveEvent; // Paper - Add EntityMoveEvent
+
+ public LevelChunk getChunkIfLoaded(int x, int z) {
+- return this.chunkSource.getChunk(x, z, false);
++ return this.chunkSource.getChunkAtIfLoadedImmediately(x, z); // Paper - Use getChunkIfLoadedImmediately
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
+index 2f82f77ba6530dcdb98037aa627ef3cead758b2e..68cee32833a3d683852e67e3727d62b84fa60cc4 100644
+--- a/src/main/java/net/minecraft/world/level/Level.java
++++ b/src/main/java/net/minecraft/world/level/Level.java
+@@ -180,6 +180,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+ public CraftServer getCraftServer() {
+ return (CraftServer) Bukkit.getServer();
+ }
++ // Paper start - Use getChunkIfLoadedImmediately
++ @Override
++ public boolean hasChunk(int chunkX, int chunkZ) {
++ return this.getChunkIfLoaded(chunkX, chunkZ) != null;
++ }
++ // Paper end - Use getChunkIfLoadedImmediately
++
+
+ public abstract ResourceKey<LevelStem> getTypeKey();
+
+diff --git a/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java b/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java
+index 13b34e89bd3e55df1bb1d4d0cf013bafae43f502..df6c97be1b278c97a20390be5d3e60f429383702 100644
+--- a/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java
++++ b/src/main/java/net/minecraft/world/level/gameevent/GameEventDispatcher.java
+@@ -56,7 +56,7 @@ public class GameEventDispatcher {
+
+ for (int l1 = j; l1 <= i1; ++l1) {
+ for (int i2 = l; i2 <= k1; ++i2) {
+- LevelChunk chunk = this.level.getChunkSource().getChunkNow(l1, i2);
++ LevelChunk chunk = (LevelChunk) this.level.getChunkIfLoadedImmediately(l1, i2); // Paper - Use getChunkIfLoadedImmediately
+
+ if (chunk != null) {
+ for (int j2 = k; j2 <= j1; ++j2) {
diff --git a/patches/server/0553-Fix-commands-from-signs-not-firing-command-events.patch b/patches/server/0553-Fix-commands-from-signs-not-firing-command-events.patch
new file mode 100644
index 0000000000..c851f91672
--- /dev/null
+++ b/patches/server/0553-Fix-commands-from-signs-not-firing-command-events.patch
@@ -0,0 +1,121 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Fri, 9 Jul 2021 13:50:48 -0700
+Subject: [PATCH] Fix commands from signs not firing command events
+
+This patch changes sign command logic so that `run_command` click events:
+ - are logged to the console
+ - fire PlayerCommandPreprocessEvent
+ - work with double-slash commands like `//wand`
+ - sends failure messages to the player who clicked the sign
+
+diff --git a/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java b/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..01a2bc1feec808790bb93618ce46adb9bea5a9c8
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/commands/DelegatingCommandSource.java
+@@ -0,0 +1,42 @@
++package io.papermc.paper.commands;
++
++import net.minecraft.commands.CommandSource;
++import net.minecraft.commands.CommandSourceStack;
++import net.minecraft.network.chat.Component;
++import org.bukkit.command.CommandSender;
++
++import java.util.UUID;
++
++public class DelegatingCommandSource implements CommandSource {
++
++ private final CommandSource delegate;
++
++ public DelegatingCommandSource(CommandSource delegate) {
++ this.delegate = delegate;
++ }
++
++ @Override
++ public void sendSystemMessage(Component message) {
++ delegate.sendSystemMessage(message);
++ }
++
++ @Override
++ public boolean acceptsSuccess() {
++ return delegate.acceptsSuccess();
++ }
++
++ @Override
++ public boolean acceptsFailure() {
++ return delegate.acceptsFailure();
++ }
++
++ @Override
++ public boolean shouldInformAdmins() {
++ return delegate.shouldInformAdmins();
++ }
++
++ @Override
++ public CommandSender getBukkitSender(CommandSourceStack wrapper) {
++ return delegate.getBukkitSender(wrapper);
++ }
++}
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java
+index 9861388bdca8d32b7e9a49a251088c98283d8234..86550d200922ee313019a21fe593c594c967f28b 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/SignBlockEntity.java
+@@ -274,7 +274,17 @@ public class SignBlockEntity extends BlockEntity {
+ ClickEvent chatclickable = chatmodifier.getClickEvent();
+
+ if (chatclickable != null && chatclickable.getAction() == ClickEvent.Action.RUN_COMMAND) {
+- player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(player, world, pos), chatclickable.getValue());
++ // Paper start - Fix commands from signs not firing command events
++ String command = chatclickable.getValue().startsWith("/") ? chatclickable.getValue() : "/" + chatclickable.getValue();
++ if (org.spigotmc.SpigotConfig.logCommands) {
++ LOGGER.info("{} issued server command: {}", player.getScoreboardName(), command);
++ }
++ io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent event = new io.papermc.paper.event.player.PlayerSignCommandPreprocessEvent((org.bukkit.entity.Player) player.getBukkitEntity(), command, new org.bukkit.craftbukkit.util.LazyPlayerSet(player.getServer()), (org.bukkit.block.Sign) CraftBlock.at(this.level, this.worldPosition).getState(), front ? Side.FRONT : Side.BACK);
++ if (!event.callEvent()) {
++ return false;
++ }
++ player.getServer().getCommands().performPrefixedCommand(this.createCommandSourceStack(((org.bukkit.craftbukkit.entity.CraftPlayer) event.getPlayer()).getHandle(), world, pos), event.getMessage());
++ // Paper end - Fix commands from signs not firing command events
+ flag1 = true;
+ }
+ }
+@@ -314,8 +324,23 @@ public class SignBlockEntity extends BlockEntity {
+ String s = player == null ? "Sign" : player.getName().getString();
+ Object object = player == null ? Component.literal("Sign") : player.getDisplayName();
+
+- // CraftBukkit - commandSource
+- return new CommandSourceStack(this.commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player);
++ // Paper start - Fix commands from signs not firing command events
++ CommandSource commandSource = this.level.paperConfig().misc.showSignClickCommandFailureMsgsToPlayer ? new io.papermc.paper.commands.DelegatingCommandSource(this.commandSource) {
++ @Override
++ public void sendSystemMessage(Component message) {
++ if (player instanceof final ServerPlayer serverPlayer) {
++ serverPlayer.sendSystemMessage(message);
++ }
++ }
++
++ @Override
++ public boolean acceptsFailure() {
++ return true;
++ }
++ } : this.commandSource;
++ // Paper end - Fix commands from signs not firing command events
++ // CraftBukkit - this
++ return new CommandSourceStack(commandSource, Vec3.atCenterOf(pos), Vec2.ZERO, (ServerLevel) world, 2, s, (Component) object, world.getServer(), player); // Paper - Fix commands from signs not firing command events
+ }
+
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java
+index d113e54a30db16e2ad955170df6030d15de530d6..21b6f90cf5bd7087d1a0f512289d971f2c3e1afa 100644
+--- a/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java
++++ b/src/main/java/org/bukkit/craftbukkit/command/BukkitCommandWrapper.java
+@@ -61,7 +61,7 @@ public class BukkitCommandWrapper implements com.mojang.brigadier.Command<Comman
+ CommandSender sender = context.getSource().getBukkitSender();
+
+ try {
+- return this.server.dispatchCommand(sender, context.getInput()) ? 1 : 0;
++ return this.server.dispatchCommand(sender, context.getRange().get(context.getInput())) ? 1 : 0; // Paper - Fix commands from signs not firing command events; actually use the StringRange from context
+ } catch (CommandException ex) {
+ sender.sendMessage(org.bukkit.ChatColor.RED + "An internal error occurred while attempting to perform this command");
+ this.server.getLogger().log(Level.SEVERE, null, ex);
diff --git a/patches/server/0554-Add-PlayerArmSwingEvent.patch b/patches/server/0554-Add-PlayerArmSwingEvent.patch
new file mode 100644
index 0000000000..e08349e079
--- /dev/null
+++ b/patches/server/0554-Add-PlayerArmSwingEvent.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Fri, 12 Mar 2021 19:22:21 -0800
+Subject: [PATCH] Add PlayerArmSwingEvent
+
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index fffefbed068a339f0db607d6734be611b273bbcf..0a5abb1a580310cc26ce3d64e6f878bde3bbfd0a 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -2488,7 +2488,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ } // Paper end - Call interact event
+
+ // Arm swing animation
+- PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == InteractionHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING);
++ io.papermc.paper.event.player.PlayerArmSwingEvent event = new io.papermc.paper.event.player.PlayerArmSwingEvent(this.getCraftPlayer(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(packet.getHand())); // Paper - Add PlayerArmSwingEvent
+ this.cserver.getPluginManager().callEvent(event);
+
+ if (event.isCancelled()) return;
diff --git a/patches/server/0555-Fix-kick-event-leave-message-not-being-sent.patch b/patches/server/0555-Fix-kick-event-leave-message-not-being-sent.patch
new file mode 100644
index 0000000000..ff8a83c615
--- /dev/null
+++ b/patches/server/0555-Fix-kick-event-leave-message-not-being-sent.patch
@@ -0,0 +1,127 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 7 Jul 2021 16:19:41 -0700
+Subject: [PATCH] Fix kick event leave message not being sent
+
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index b1224b0ef12a746477047073f5bb94405871ce85..6c3ec21bb61e15becb35c01112770471c2364c5f 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -316,7 +316,6 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ public boolean joining = true;
+ public boolean sentListPacket = false;
+ public boolean supressTrackerForLogin = false; // Paper - Fire PlayerJoinEvent when Player is actually ready
+- public String kickLeaveMessage = null; // SPIGOT-3034: Forward leave message to PlayerQuitEvent
+ // CraftBukkit end
+ public boolean isRealPlayer; // Paper
+ public com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent
+diff --git a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+index 59d20fd62e850a38380d877cef95ed69cb46ecbd..fc242acade3ff06c9213428cde103cf078216382 100644
+--- a/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+@@ -116,6 +116,11 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+
+ @Override
+ public void onDisconnect(DisconnectionDetails info) {
++ // Paper start - Fix kick event leave message not being sent
++ this.onDisconnect(info, null);
++ }
++ public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) {
++ // Paper end - Fix kick event leave message not being sent
+ if (this.isSingleplayerOwner()) {
+ ServerCommonPacketListenerImpl.LOGGER.info("Stopping singleplayer server as player logged out");
+ this.server.halt(false);
+@@ -386,18 +391,17 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack
+ // Do not kick the player
+ return;
+ }
+- this.player.kickLeaveMessage = event.getLeaveMessage(); // CraftBukkit - SPIGOT-3034: Forward leave message to PlayerQuitEvent
+ // Send the possibly modified leave message
+- this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionInfo.report(), disconnectionInfo.bugReportLink())); // Paper - Adventure
++ this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionInfo.report(), disconnectionInfo.bugReportLink()), event.leaveMessage()); // Paper - Adventure & use kick event leave message
+ }
+
+- private void disconnect0(DisconnectionDetails disconnectiondetails) {
++ private void disconnect0(DisconnectionDetails disconnectiondetails, @Nullable net.kyori.adventure.text.Component leaveMessage) { // Paper - use kick event leave message
+ // CraftBukkit end
+ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason
+ this.connection.send(new ClientboundDisconnectPacket(disconnectiondetails.reason()), PacketSendListener.thenRun(() -> {
+ this.connection.disconnect(disconnectiondetails);
+ }));
+- this.onDisconnect(disconnectiondetails); // CraftBukkit - fire quit instantly
++ this.onDisconnect(disconnectiondetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message
+ this.connection.setReadOnly();
+ MinecraftServer minecraftserver = this.server;
+ Connection networkmanager = this.connection;
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 0a5abb1a580310cc26ce3d64e6f878bde3bbfd0a..1fb600d3e0f14940d911881ebbe24a6b57d3a81c 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -1975,6 +1975,12 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+
+ @Override
+ public void onDisconnect(DisconnectionDetails info) {
++ // Paper start - Fix kick event leave message not being sent
++ this.onDisconnect(info, null);
++ }
++ @Override
++ public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) {
++ // Paper end - Fix kick event leave message not being sent
+ // CraftBukkit start - Rarely it would send a disconnect line twice
+ if (this.processedDisconnect) {
+ return;
+@@ -1983,11 +1989,17 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ }
+ // CraftBukkit end
+ ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), info.reason().getString());
+- this.removePlayerFromWorld();
+- super.onDisconnect(info);
++ this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent
++ super.onDisconnect(info, quitMessage); // Paper - Fix kick event leave message not being sent
+ }
+
++ // Paper start - Fix kick event leave message not being sent
+ private void removePlayerFromWorld() {
++ this.removePlayerFromWorld(null);
++ }
++
++ private void removePlayerFromWorld(@Nullable net.kyori.adventure.text.Component quitMessage) {
++ // Paper end - Fix kick event leave message not being sent
+ this.chatMessageChain.close();
+ // CraftBukkit start - Replace vanilla quit message handling with our own.
+ /*
+@@ -1997,7 +2009,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+
+ this.player.disconnect();
+ // Paper start - Adventure
+- net.kyori.adventure.text.Component quitMessage = this.server.getPlayerList().remove(this.player);
++ quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used
+ if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) {
+ this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false);
+ // Paper end
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 26ba0cec3a8492d91df894a69cc1fc8076eeda0d..85bd61bd45690c5532f56d8f11b81f7a11f3e284 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -510,6 +510,11 @@ public abstract class PlayerList {
+ }
+
+ public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer) { // CraftBukkit - return string // Paper - return Component
++ // Paper start - Fix kick event leave message not being sent
++ return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName())));
++ }
++ public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer, net.kyori.adventure.text.Component leaveMessage) {
++ // Paper end - Fix kick event leave message not being sent
+ ServerLevel worldserver = entityplayer.serverLevel();
+
+ entityplayer.awardStat(Stats.LEAVE_GAME);
+@@ -520,7 +525,7 @@ public abstract class PlayerList {
+ entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); // Paper - Inventory close reason
+ }
+
+- PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName())), entityplayer.quitReason); // Paper - Adventure & Add API for quit reason
++ PlayerQuitEvent playerQuitEvent = new PlayerQuitEvent(entityplayer.getBukkitEntity(), leaveMessage, entityplayer.quitReason); // Paper - Adventure & Add API for quit reason
+ this.cserver.getPluginManager().callEvent(playerQuitEvent);
+ entityplayer.getBukkitEntity().disconnect(playerQuitEvent.getQuitMessage());
+
diff --git a/patches/server/0556-Don-t-apply-cramming-damage-to-players.patch b/patches/server/0556-Don-t-apply-cramming-damage-to-players.patch
new file mode 100644
index 0000000000..14f5ce16cf
--- /dev/null
+++ b/patches/server/0556-Don-t-apply-cramming-damage-to-players.patch
@@ -0,0 +1,25 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Phoenix616 <[email protected]>
+Date: Sun, 20 Jun 2021 16:35:42 +0100
+Subject: [PATCH] Don't apply cramming damage to players
+
+It does not make a lot of sense to damage players if they get crammed,
+ especially as the usecase of teleporting lots of players to the same
+ location isn't too uncommon and killing all those players isn't
+ really what one would expect to happen.
+
+For those who really want it a config option is provided.
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index 6c3ec21bb61e15becb35c01112770471c2364c5f..4c18d13fdc2221342adb390a6b68be871036c87c 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -1805,7 +1805,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+
+ @Override
+ public boolean isInvulnerableTo(ServerLevel world, DamageSource source) {
+- return super.isInvulnerableTo(world, source) || this.isChangingDimension() && !source.is(DamageTypes.ENDER_PEARL) || !this.hasClientLoaded();
++ return (super.isInvulnerableTo(world, source) || this.isChangingDimension() && !source.is(DamageTypes.ENDER_PEARL) || !this.hasClientLoaded()) || (!this.level().paperConfig().collisions.allowPlayerCrammingDamage && source.is(DamageTypes.CRAMMING)); // Paper - disable player cramming
+ }
+
+ @Override
diff --git a/patches/server/0557-Rate-options-and-timings-for-sensors-and-behaviors.patch b/patches/server/0557-Rate-options-and-timings-for-sensors-and-behaviors.patch
new file mode 100644
index 0000000000..3473ea4ceb
--- /dev/null
+++ b/patches/server/0557-Rate-options-and-timings-for-sensors-and-behaviors.patch
@@ -0,0 +1,88 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Phoenix616 <[email protected]>
+Date: Mon, 28 Jun 2021 22:38:29 +0100
+Subject: [PATCH] Rate options and timings for sensors and behaviors
+
+This adds config options to specify the tick rate for sensors
+ and behaviors of different entity types as well as timings
+ for those in order to be able to have some metrics as to which
+ ones might need tweaking.
+
+diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java
+index f639cafa64d98a001e622882c647701547f5c3ac..ba951cc1aaa94b58ee7985f197d41cc8be747fc8 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java
++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/Behavior.java
+@@ -14,6 +14,9 @@ public abstract class Behavior<E extends LivingEntity> implements BehaviorContro
+ private long endTimestamp;
+ private final int minDuration;
+ private final int maxDuration;
++ // Paper start - configurable behavior tick rate and timings
++ private final String configKey;
++ // Paper end - configurable behavior tick rate and timings
+
+ public Behavior(Map<MemoryModuleType<?>, MemoryStatus> requiredMemoryState) {
+ this(requiredMemoryState, 60);
+@@ -27,6 +30,14 @@ public abstract class Behavior<E extends LivingEntity> implements BehaviorContro
+ this.minDuration = minRunTime;
+ this.maxDuration = maxRunTime;
+ this.entryCondition = requiredMemoryState;
++ // Paper start - configurable behavior tick rate and timings
++ String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName();
++ int lastSeparator = key.lastIndexOf('.');
++ if (lastSeparator != -1) {
++ key = key.substring(lastSeparator + 1);
++ }
++ this.configKey = key.toLowerCase(java.util.Locale.ROOT);
++ // Paper end - configurable behavior tick rate and timings
+ }
+
+ @Override
+@@ -36,6 +47,12 @@ public abstract class Behavior<E extends LivingEntity> implements BehaviorContro
+
+ @Override
+ public final boolean tryStart(ServerLevel world, E entity, long time) {
++ // Paper start - configurable behavior tick rate and timings
++ int tickRate = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.behavior.get(entity.getType(), this.configKey), -1);
++ if (tickRate > -1 && time < this.endTimestamp + tickRate) {
++ return false;
++ }
++ // Paper end - configurable behavior tick rate and timings
+ if (this.hasRequiredMemories(entity) && this.checkExtraStartConditions(world, entity)) {
+ this.status = Behavior.Status.RUNNING;
+ int i = this.minDuration + world.getRandom().nextInt(this.maxDuration + 1 - this.minDuration);
+diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java
+index 4d451f6cb5862411848bb9b6b5692ab512dcaa25..fb1f5375eafb030ae08c735a80e9c8149726cda4 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java
++++ b/src/main/java/net/minecraft/world/entity/ai/sensing/Sensor.java
+@@ -29,8 +29,19 @@ public abstract class Sensor<E extends LivingEntity> {
+ .ignoreInvisibilityTesting();
+ private final int scanRate;
+ private long timeToTick;
++ // Paper start - configurable sensor tick rate and timings
++ private final String configKey;
++ // Paper end
+
+ public Sensor(int senseInterval) {
++ // Paper start - configurable sensor tick rate and timings
++ String key = io.papermc.paper.util.MappingEnvironment.reobf() ? io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(this.getClass().getName()) : this.getClass().getName();
++ int lastSeparator = key.lastIndexOf('.');
++ if (lastSeparator != -1) {
++ key = key.substring(lastSeparator + 1);
++ }
++ this.configKey = key.toLowerCase(java.util.Locale.ROOT);
++ // Paper end
+ this.scanRate = senseInterval;
+ this.timeToTick = (long)RANDOM.nextInt(senseInterval);
+ }
+@@ -41,8 +52,10 @@ public abstract class Sensor<E extends LivingEntity> {
+
+ public final void tick(ServerLevel world, E entity) {
+ if (--this.timeToTick <= 0L) {
+- this.timeToTick = (long)this.scanRate;
++ // Paper start - configurable sensor tick rate and timings
++ this.timeToTick = java.util.Objects.requireNonNullElse(world.paperConfig().tickRates.sensor.get(entity.getType(), this.configKey), this.scanRate);
+ this.updateTargetingConditionRanges(entity);
++ // Paper end
+ this.doTick(world, entity);
+ }
+ }
diff --git a/patches/server/0558-Add-missing-forceDrop-toggles.patch b/patches/server/0558-Add-missing-forceDrop-toggles.patch
new file mode 100644
index 0000000000..aa609aadba
--- /dev/null
+++ b/patches/server/0558-Add-missing-forceDrop-toggles.patch
@@ -0,0 +1,124 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Tue, 20 Jul 2021 21:25:35 -0700
+Subject: [PATCH] Add missing forceDrop toggles
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java b/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java
+index 4ec1f881c05d96d72814ac3dffd3b4bef40c1bce..c34cb8c918e400636856317cc58356d2677e1d52 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java
++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/WorkAtComposter.java
+@@ -86,7 +86,9 @@ public class WorkAtComposter extends WorkAtPoi {
+ simpleContainer.removeItemType(Items.WHEAT, m);
+ ItemStack itemStack = simpleContainer.addItem(new ItemStack(Items.BREAD, l));
+ if (!itemStack.isEmpty()) {
++ villager.forceDrops = true; // Paper - Add missing forceDrop toggles
+ villager.spawnAtLocation(world, itemStack, 0.5F);
++ villager.forceDrops = false; // Paper - Add missing forceDrop toggles
+ }
+ }
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/animal/Panda.java b/src/main/java/net/minecraft/world/entity/animal/Panda.java
+index 705c26ceff9371b09311bd7fa796c0efde7ebfee..4f04170b3ec4ff59358e10ccfd0799af3ab590c3 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/Panda.java
++++ b/src/main/java/net/minecraft/world/entity/animal/Panda.java
+@@ -540,7 +540,9 @@ public class Panda extends Animal {
+
+ if (world1 instanceof ServerLevel worldserver) {
+ if (worldserver.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) {
++ this.forceDrops = true; // Paper - Add missing forceDrop toggles
+ this.dropFromGiftLootTable(worldserver, BuiltInLootTables.PANDA_SNEEZE, this::spawnAtLocation);
++ this.forceDrops = false; // Paper - Add missing forceDrop toggles
+ }
+ }
+
+@@ -664,7 +666,9 @@ public class Panda extends Animal {
+ ItemStack itemstack1 = this.getItemBySlot(EquipmentSlot.MAINHAND);
+
+ if (!itemstack1.isEmpty() && !player.hasInfiniteMaterials()) {
++ this.forceDrops = true; // Paper - Add missing forceDrop toggles
+ this.spawnAtLocation(worldserver, itemstack1);
++ this.forceDrops = false; // Paper - Add missing forceDrop toggles
+ }
+
+ this.setItemSlot(EquipmentSlot.MAINHAND, new ItemStack(itemstack.getItem(), 1));
+@@ -942,7 +946,9 @@ public class Panda extends Animal {
+ ItemStack itemstack = Panda.this.getItemBySlot(EquipmentSlot.MAINHAND);
+
+ if (!itemstack.isEmpty()) {
++ Panda.this.forceDrops = true; // Paper - Add missing forceDrop toggles
+ Panda.this.spawnAtLocation(getServerLevel(Panda.this.level()), itemstack);
++ Panda.this.forceDrops = false; // Paper - Add missing forceDrop toggles
+ Panda.this.setItemSlot(EquipmentSlot.MAINHAND, ItemStack.EMPTY);
+ int i = Panda.this.isLazy() ? Panda.this.random.nextInt(50) + 10 : Panda.this.random.nextInt(150) + 10;
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Bogged.java b/src/main/java/net/minecraft/world/entity/monster/Bogged.java
+index 9d416f775fa19ad1978c7c9c9e0d5bc16728879d..be029746905aeba218684b883282649089657de3 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Bogged.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Bogged.java
+@@ -145,9 +145,11 @@ public class Bogged extends AbstractSkeleton implements Shearable {
+ }
+
+ private void spawnShearedMushrooms(ServerLevel world, ItemStack shears) {
++ this.forceDrops = true; // Paper - Add missing forceDrop toggles
+ this.dropFromShearingLootTable(world, BuiltInLootTables.BOGGED_SHEAR, shears, (worldserver1, itemstack1) -> {
+ this.spawnAtLocation(worldserver1, itemstack1, this.getBbHeight());
+ });
++ this.forceDrops = false; // Paper - Add missing forceDrop toggles
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
+index 737c1d5c8ec81d55799ed13560e5e2acc7d8f4e5..5e64a6b94a510ed618a2542ad03e406a181b63d4 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/Piglin.java
+@@ -326,9 +326,11 @@ public class Piglin extends AbstractPiglin implements CrossbowAttackMob, Invento
+ @Override
+ protected void finishConversion(ServerLevel world) {
+ PiglinAi.cancelAdmiring(world, this);
++ this.forceDrops = true; // Paper - Add missing forceDrop toggles
+ this.inventory.removeAllItems().forEach((itemstack) -> {
+ this.spawnAtLocation(world, itemstack);
+ });
++ this.forceDrops = false; // Paper - Add missing forceDrop toggles
+ super.finishConversion(world);
+ }
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
+index 42b1bd58c6e2c3bd1170171eabfefe315202f340..55868c82bf8bd61ce3494aa9f363c20c88ec6aa6 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
+@@ -273,7 +273,9 @@ public class PiglinAi {
+
+ private static void holdInOffhand(ServerLevel world, Piglin piglin, ItemStack stack) {
+ if (PiglinAi.isHoldingItemInOffHand(piglin)) {
++ piglin.forceDrops = true; // Paper - Add missing forceDrop toggles
+ piglin.spawnAtLocation(world, piglin.getItemInHand(InteractionHand.OFF_HAND));
++ piglin.forceDrops = false; // Paper - Add missing forceDrop toggles
+ }
+
+ piglin.holdInOffHand(stack);
+@@ -333,7 +335,9 @@ public class PiglinAi {
+
+ protected static void cancelAdmiring(ServerLevel world, Piglin piglin) {
+ if (PiglinAi.isAdmiringItem(piglin) && !piglin.getOffhandItem().isEmpty()) {
++ piglin.forceDrops = true; // Paper - Add missing forceDrop toggles
+ piglin.spawnAtLocation(world, piglin.getOffhandItem());
++ piglin.forceDrops = false; // Paper - Add missing forceDrop toggles
+ piglin.setItemInHand(InteractionHand.OFF_HAND, ItemStack.EMPTY);
+ }
+
+diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java
+index b6f1d989df3811f423d1cdff98b05ecc4a9268fe..6a7d9b59ff4aa2962a88ae8688c06bc67d70dfda 100644
+--- a/src/main/java/net/minecraft/world/entity/raid/Raider.java
++++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java
+@@ -233,7 +233,9 @@ public abstract class Raider extends PatrollingMonster {
+ double d0 = (double) this.getEquipmentDropChance(enumitemslot);
+
+ if (!itemstack1.isEmpty() && (double) Math.max(this.random.nextFloat() - 0.1F, 0.0F) < d0) {
++ this.forceDrops = true; // Paper - Add missing forceDrop toggles
+ this.spawnAtLocation(world, itemstack1);
++ this.forceDrops = false; // Paper - Add missing forceDrop toggles
+ }
+
+ this.onItemPickup(itemEntity);
diff --git a/patches/server/0559-Stinger-API.patch b/patches/server/0559-Stinger-API.patch
new file mode 100644
index 0000000000..9e851372d0
--- /dev/null
+++ b/patches/server/0559-Stinger-API.patch
@@ -0,0 +1,50 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Tue, 22 Jun 2021 23:15:44 -0400
+Subject: [PATCH] Stinger API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+index 6020c0c164595db4e2001edf0c1fbe99ed87682d..5d6e4f2aeca9be4dd4504bc93b006e89ef875931 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+@@ -384,6 +384,39 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+ public boolean isInvulnerable() {
+ return this.getHandle().isInvulnerableTo((ServerLevel) this.getHandle().level(), this.getHandle().damageSources().generic());
+ }
++ // Paper start - Bee Stinger API
++ @Override
++ public int getBeeStingerCooldown() {
++ return getHandle().removeStingerTime;
++ }
++
++ @Override
++ public void setBeeStingerCooldown(int ticks) {
++ getHandle().removeStingerTime = ticks;
++ }
++
++ @Override
++ public int getBeeStingersInBody() {
++ return getHandle().getStingerCount();
++ }
++
++ @Override
++ public void setBeeStingersInBody(int count) {
++ Preconditions.checkArgument(count >= 0, "New bee stinger amount must be >= 0");
++ getHandle().setStingerCount(count);
++ }
++
++ @Override
++ public void setNextBeeStingerRemoval(final int ticks) {
++ Preconditions.checkArgument(ticks >= 0, "New amount of ticks before next bee stinger removal must be >= 0");
++ this.getHandle().removeStingerTime = ticks;
++ }
++
++ @Override
++ public int getNextBeeStingerRemoval() {
++ return this.getHandle().removeStingerTime;
++ }
++ // Paper end - Bee Stinger API
+
+ @Override
+ public void damage(double amount) {
diff --git a/patches/server/0560-Add-System.out-err-catcher.patch b/patches/server/0560-Add-System.out-err-catcher.patch
new file mode 100644
index 0000000000..8b01f8520a
--- /dev/null
+++ b/patches/server/0560-Add-System.out-err-catcher.patch
@@ -0,0 +1,118 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: underscore11code <[email protected]>
+Date: Fri, 23 Jul 2021 23:01:42 -0700
+Subject: [PATCH] Add System.out/err catcher
+
+
+diff --git a/src/main/java/io/papermc/paper/logging/SysoutCatcher.java b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..a8e813ca89b033f061e695288b3383bdcf128531
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/logging/SysoutCatcher.java
+@@ -0,0 +1,94 @@
++package io.papermc.paper.logging;
++
++import org.bukkit.Bukkit;
++import org.bukkit.plugin.java.JavaPlugin;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++import java.io.OutputStream;
++import java.io.PrintStream;
++import java.util.Objects;
++import java.util.concurrent.ConcurrentHashMap;
++import java.util.concurrent.ConcurrentMap;
++import java.util.concurrent.TimeUnit;
++import java.util.logging.Level;
++
++public final class SysoutCatcher {
++ private static final boolean SUPPRESS_NAGS = Boolean.getBoolean("io.papermc.paper.suppress.sout.nags");
++ // Nanoseconds between nag at most; if interval is caught first, this is reset.
++ // <= 0 for disabling.
++ private static final long NAG_TIMEOUT = TimeUnit.MILLISECONDS.toNanos(
++ Long.getLong("io.papermc.paper.sout.nags.timeout", TimeUnit.MINUTES.toMillis(5L)));
++ // Count since last nag; if timeout is first, this is reset.
++ // <= 0 for disabling.
++ private static final long NAG_INTERVAL = Long.getLong("io.papermc.paper.sout.nags.interval", 200L);
++
++ // We don't particularly care about how correct this is at any given moment; let's do it on a best attempt basis.
++ // The records are also pretty small, so let's just go for a size of 64 to start...
++ //
++ // Content: Plugin name => nag object
++ // Why plugin name?: This doesn't store a reference to the plugin; keeps the reload ability.
++ // Why not clean on reload?: Effort.
++ private final ConcurrentMap<String, PluginNag> nagRecords = new ConcurrentHashMap<>(64);
++
++ public SysoutCatcher() {
++ System.setOut(new WrappedOutStream(System.out, Level.INFO, "[STDOUT] "));
++ System.setErr(new WrappedOutStream(System.err, Level.SEVERE, "[STDERR] "));
++ }
++
++ private final class WrappedOutStream extends PrintStream {
++ private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
++ private final Level level;
++ private final String prefix;
++
++ public WrappedOutStream(@NotNull final OutputStream out, final Level level, final String prefix) {
++ super(out);
++ this.level = level;
++ this.prefix = prefix;
++ }
++
++ @Override
++ public void println(@Nullable final String line) {
++ final Class<?> clazz = STACK_WALKER.getCallerClass();
++ try {
++ final JavaPlugin plugin = JavaPlugin.getProvidingPlugin(clazz);
++
++ // Instead of just printing the message, send it to the plugin's logger
++ plugin.getLogger().log(this.level, this.prefix + line);
++
++ if (SysoutCatcher.SUPPRESS_NAGS) {
++ return;
++ }
++ if (SysoutCatcher.NAG_INTERVAL > 0 || SysoutCatcher.NAG_TIMEOUT > 0) {
++ final PluginNag nagRecord = SysoutCatcher.this.nagRecords.computeIfAbsent(plugin.getName(), k -> new PluginNag());
++ final boolean hasTimePassed = SysoutCatcher.NAG_TIMEOUT > 0
++ && (nagRecord.lastNagTimestamp == Long.MIN_VALUE
++ || nagRecord.lastNagTimestamp + SysoutCatcher.NAG_TIMEOUT <= System.nanoTime());
++ final boolean hasMessagesPassed = SysoutCatcher.NAG_INTERVAL > 0
++ && (nagRecord.messagesSinceNag == Long.MIN_VALUE
++ || ++nagRecord.messagesSinceNag >= SysoutCatcher.NAG_INTERVAL);
++ if (!hasMessagesPassed && !hasTimePassed) {
++ return;
++ }
++ nagRecord.lastNagTimestamp = System.nanoTime();
++ nagRecord.messagesSinceNag = 0;
++ }
++ Bukkit.getLogger().warning(
++ String.format("Nag author(s): '%s' of '%s' about their usage of System.out/err.print. "
++ + "Please use your plugin's logger instead (JavaPlugin#getLogger).",
++ plugin.getPluginMeta().getAuthors(),
++ plugin.getPluginMeta().getDisplayName())
++ );
++ } catch (final IllegalArgumentException | IllegalStateException e) {
++ // If anything happens, the calling class doesn't exist, there is no JavaPlugin that "owns" the calling class, etc
++ // Just print out normally, with some added information
++ Bukkit.getLogger().log(this.level, String.format("%s[%s] %s", this.prefix, clazz.getName(), line));
++ }
++ }
++ }
++
++ private static class PluginNag {
++ private long lastNagTimestamp = Long.MIN_VALUE;
++ private long messagesSinceNag = Long.MIN_VALUE;
++ }
++}
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index b4af066a21e4893b5ec146d109b5146b6996a0cf..45d887f4143444321f563cdd7d084b2b9ccf911e 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -310,6 +310,7 @@ public final class CraftServer implements Server {
+ public Set<String> activeCompatibilities = Collections.emptySet();
+ private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper
+ public static Exception excessiveVelEx; // Paper - Velocity warnings
++ private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper
+
+ static {
+ ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
diff --git a/patches/server/0561-Prevent-AFK-kick-while-watching-end-credits.patch b/patches/server/0561-Prevent-AFK-kick-while-watching-end-credits.patch
new file mode 100644
index 0000000000..75bfbb054a
--- /dev/null
+++ b/patches/server/0561-Prevent-AFK-kick-while-watching-end-credits.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Noah van der Aa <[email protected]>
+Date: Sat, 24 Jul 2021 16:54:11 +0200
+Subject: [PATCH] Prevent AFK kick while watching end credits
+
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 1fb600d3e0f14940d911881ebbe24a6b57d3a81c..2df548b68e56a309af4b89f8f1174dde3b046e7d 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -398,7 +398,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ this.tabSpamThrottler.tick(); // Paper - configurable tab spam limits
+ this.recipeSpamPackets.tick(); // Paper - auto recipe limit
+ this.dropSpamThrottler.tick();
+- if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) {
++ if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits
+ this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854
+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause
+ }
diff --git a/patches/server/0562-Allow-skipping-writing-of-comments-to-server.propert.patch b/patches/server/0562-Allow-skipping-writing-of-comments-to-server.propert.patch
new file mode 100644
index 0000000000..1fce522892
--- /dev/null
+++ b/patches/server/0562-Allow-skipping-writing-of-comments-to-server.propert.patch
@@ -0,0 +1,69 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Professor Bloodstone <[email protected]>
+Date: Fri, 23 Jul 2021 02:32:04 +0200
+Subject: [PATCH] Allow skipping writing of comments to server.properties
+
+Makes less git noise, as it won't update the date every single time
+
+Use -DPaper.skipServerPropertiesComments=true flag to disable writing it
+
+diff --git a/src/main/java/net/minecraft/server/dedicated/Settings.java b/src/main/java/net/minecraft/server/dedicated/Settings.java
+index 0ec3b546db0cf3858dd9cd9ea067d1d6713a8491..d7bd235ef2815890e038091dd625177049d253a5 100644
+--- a/src/main/java/net/minecraft/server/dedicated/Settings.java
++++ b/src/main/java/net/minecraft/server/dedicated/Settings.java
+@@ -29,6 +29,7 @@ public abstract class Settings<T extends Settings<T>> {
+
+ private static final Logger LOGGER = LogUtils.getLogger();
+ public final Properties properties;
++ private static final boolean skipComments = Boolean.getBoolean("Paper.skipServerPropertiesComments"); // Paper - allow skipping server.properties comments
+ // CraftBukkit start
+ private OptionSet options = null;
+
+@@ -123,7 +124,46 @@ public abstract class Settings<T extends Settings<T>> {
+ return;
+ }
+ // CraftBukkit end
+- BufferedWriter bufferedwriter = Files.newBufferedWriter(path, StandardCharsets.UTF_8);
++ // Paper start - allow skipping server.properties comments
++ java.io.OutputStream outputstream = Files.newOutputStream(path);
++ java.io.BufferedOutputStream bufferedOutputStream = !skipComments ? new java.io.BufferedOutputStream(outputstream) : new java.io.BufferedOutputStream(outputstream) {
++ private boolean isRightAfterNewline = true; // If last written char was newline
++ private boolean isComment = false; // Are we writing comment currently?
++
++ @Override
++ public void write(@org.jetbrains.annotations.NotNull byte[] b) throws IOException {
++ this.write(b, 0, b.length);
++ }
++
++ @Override
++ public void write(@org.jetbrains.annotations.NotNull byte[] bbuf, int off, int len) throws IOException {
++ int latest_offset = off; // The latest offset, updated when comment ends
++ for (int index = off; index < off + len; ++index ) {
++ byte c = bbuf[index];
++ boolean isNewline = (c == '\n' || c == '\r');
++ if (isNewline && this.isComment) {
++ // Comment has ended
++ this.isComment = false;
++ latest_offset = index+1;
++ }
++ if (c == '#' && this.isRightAfterNewline) {
++ this.isComment = true;
++ if (index != latest_offset) {
++ // We got some non-comment data earlier
++ super.write(bbuf, latest_offset, index-latest_offset);
++ }
++ }
++ this.isRightAfterNewline = isNewline; // Store for next iteration
++
++ }
++ if (latest_offset < off+len && !this.isComment) {
++ // We have some unwritten data, that isn't part of a comment
++ super.write(bbuf, latest_offset, (off + len) - latest_offset);
++ }
++ }
++ };
++ BufferedWriter bufferedwriter = new BufferedWriter(new java.io.OutputStreamWriter(bufferedOutputStream, java.nio.charset.StandardCharsets.UTF_8.newEncoder()));
++ // Paper end - allow skipping server.properties comments
+
+ try {
+ this.properties.store(bufferedwriter, "Minecraft server properties");
diff --git a/patches/server/0563-Add-PlayerSetSpawnEvent.patch b/patches/server/0563-Add-PlayerSetSpawnEvent.patch
new file mode 100644
index 0000000000..c4df15cc88
--- /dev/null
+++ b/patches/server/0563-Add-PlayerSetSpawnEvent.patch
@@ -0,0 +1,204 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 19 May 2021 18:59:10 -0700
+Subject: [PATCH] Add PlayerSetSpawnEvent
+
+
+diff --git a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java
+index a2d0699e8427b2262a2396495111125eccafbb66..15db9368227dbc29d07d74e85bd126b345b526b6 100644
+--- a/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java
++++ b/src/main/java/net/minecraft/server/commands/SetSpawnCommand.java
+@@ -38,24 +38,34 @@ public class SetSpawnCommand {
+ ResourceKey<Level> resourcekey = source.getLevel().dimension();
+ Iterator iterator = targets.iterator();
+
++ final Collection<ServerPlayer> actualTargets = new java.util.ArrayList<>(); // Paper - Add PlayerSetSpawnEvent
+ while (iterator.hasNext()) {
+ ServerPlayer entityplayer = (ServerPlayer) iterator.next();
+
+- entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.COMMAND); // CraftBukkit
++ // Paper start - Add PlayerSetSpawnEvent
++ if (entityplayer.setRespawnPosition(resourcekey, pos, angle, true, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.COMMAND)) {
++ actualTargets.add(entityplayer);
++ }
++ // Paper end - Add PlayerSetSpawnEvent
+ }
++ // Paper start - Add PlayerSetSpawnEvent
++ if (actualTargets.isEmpty()) {
++ return 0;
++ }
++ // Paper end - Add PlayerSetSpawnEvent
+
+ String s = resourcekey.location().toString();
+
+- if (targets.size() == 1) {
++ if (actualTargets.size() == 1) { // Paper - Add PlayerSetSpawnEvent
+ source.sendSuccess(() -> {
+- return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) targets.iterator().next()).getDisplayName());
++ return Component.translatable("commands.spawnpoint.success.single", pos.getX(), pos.getY(), pos.getZ(), angle, s, ((ServerPlayer) actualTargets.iterator().next()).getDisplayName()); // Paper - Add PlayerSetSpawnEvent
+ }, true);
+ } else {
+ source.sendSuccess(() -> {
+- return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, targets.size());
++ return Component.translatable("commands.spawnpoint.success.multiple", pos.getX(), pos.getY(), pos.getZ(), angle, s, actualTargets.size()); // Paper - Add PlayerSetSpawnEvent
+ }, true);
+ }
+
+- return targets.size();
++ return actualTargets.size(); // Paper - Add PlayerSetSpawnEvent
+ }
+ }
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index 4c18d13fdc2221342adb390a6b68be871036c87c..f2cc608d2cf040be2912b604f0d6cab21e33ded0 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -1681,7 +1681,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ } else if (this.bedBlocked(blockposition, enumdirection)) {
+ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.OBSTRUCTED);
+ } else {
+- this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, PlayerSpawnChangeEvent.Cause.BED); // CraftBukkit
++ this.setRespawnPosition(this.level().dimension(), blockposition, this.getYRot(), false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.BED); // Paper - Add PlayerSetSpawnEvent
+ if (this.level().isDay()) {
+ return Either.left(net.minecraft.world.entity.player.Player.BedSleepingProblem.NOT_POSSIBLE_NOW);
+ } else {
+@@ -2617,44 +2617,50 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ this.setRespawnPosition(player.getRespawnDimension(), player.getRespawnPosition(), player.getRespawnAngle(), player.isRespawnForced(), false);
+ }
+
++ @Deprecated // Paper - Add PlayerSetSpawnEvent
+ public void setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage) {
+- // CraftBukkit start
+- this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, PlayerSpawnChangeEvent.Cause.UNKNOWN);
+- }
+-
+- public void setRespawnPosition(ResourceKey<Level> resourcekey, @Nullable BlockPos blockposition, float f, boolean flag, boolean flag1, PlayerSpawnChangeEvent.Cause cause) {
+- ServerLevel newWorld = this.server.getLevel(resourcekey);
+- Location newSpawn = (blockposition != null) ? CraftLocation.toBukkit(blockposition, newWorld.getWorld(), f, 0) : null;
+-
+- PlayerSpawnChangeEvent event = new PlayerSpawnChangeEvent(this.getBukkitEntity(), newSpawn, flag, cause);
+- Bukkit.getServer().getPluginManager().callEvent(event);
+- if (event.isCancelled()) {
+- return;
+- }
+- newSpawn = event.getNewSpawn();
+- flag = event.isForced();
+-
+- if (newSpawn != null) {
+- resourcekey = ((CraftWorld) newSpawn.getWorld()).getHandle().dimension();
+- blockposition = BlockPos.containing(newSpawn.getX(), newSpawn.getY(), newSpawn.getZ());
+- f = newSpawn.getYaw();
+- } else {
+- resourcekey = Level.OVERWORLD;
+- blockposition = null;
+- f = 0.0F;
++ // Paper start - Add PlayerSetSpawnEvent
++ this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.UNKNOWN);
++ }
++ @Deprecated
++ public boolean setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, PlayerSpawnChangeEvent.Cause cause) {
++ return this.setRespawnPosition(dimension, pos, angle, forced, sendMessage, cause == PlayerSpawnChangeEvent.Cause.RESET ?
++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN : com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.valueOf(cause.name()));
++ }
++ public boolean setRespawnPosition(ResourceKey<Level> dimension, @Nullable BlockPos pos, float angle, boolean forced, boolean sendMessage, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause cause) {
++ Location spawnLoc = null;
++ boolean willNotify = false;
++ if (pos != null) {
++ boolean flag2 = pos.equals(this.respawnPosition) && dimension.equals(this.respawnDimension);
++ spawnLoc = io.papermc.paper.util.MCUtil.toLocation(this.getServer().getLevel(dimension), pos);
++ spawnLoc.setYaw(angle);
++ willNotify = sendMessage && !flag2;
++ }
++
++ PlayerSpawnChangeEvent dumbEvent = new PlayerSpawnChangeEvent(this.getBukkitEntity(), spawnLoc, forced,
++ cause == com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN ? PlayerSpawnChangeEvent.Cause.RESET : PlayerSpawnChangeEvent.Cause.valueOf(cause.name()));
++ dumbEvent.callEvent();
++
++ com.destroystokyo.paper.event.player.PlayerSetSpawnEvent event = new com.destroystokyo.paper.event.player.PlayerSetSpawnEvent(this.getBukkitEntity(), cause, dumbEvent.getNewSpawn(), dumbEvent.isForced(), willNotify, willNotify ? net.kyori.adventure.text.Component.translatable("block.minecraft.set_spawn") : null);
++ event.setCancelled(dumbEvent.isCancelled());
++ if (!event.callEvent()) {
++ return false;
+ }
+- // CraftBukkit end
+- if (blockposition != null) {
+- boolean flag2 = blockposition.equals(this.respawnPosition) && resourcekey.equals(this.respawnDimension);
++ if (event.getLocation() != null) {
++ dimension = event.getLocation().getWorld() != null ? ((CraftWorld) event.getLocation().getWorld()).getHandle().dimension() : dimension;
++ pos = io.papermc.paper.util.MCUtil.toBlockPosition(event.getLocation());
++ angle = event.getLocation().getYaw();
++ forced = event.isForced();
++ // Paper end - Add PlayerSetSpawnEvent
+
+- if (flag1 && !flag2) {
+- this.sendSystemMessage(Component.translatable("block.minecraft.set_spawn"));
++ if (event.willNotifyPlayer() && event.getNotification() != null) { // Paper - Add PlayerSetSpawnEvent
++ this.sendSystemMessage(PaperAdventure.asVanilla(event.getNotification())); // Paper - Add PlayerSetSpawnEvent
+ }
+
+- this.respawnPosition = blockposition;
+- this.respawnDimension = resourcekey;
+- this.respawnAngle = f;
+- this.respawnForced = flag;
++ this.respawnPosition = pos;
++ this.respawnDimension = dimension;
++ this.respawnAngle = angle;
++ this.respawnForced = forced;
+ } else {
+ this.respawnPosition = null;
+ this.respawnDimension = Level.OVERWORLD;
+@@ -2662,6 +2668,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ this.respawnForced = false;
+ }
+
++ return true; // Paper - Add PlayerSetSpawnEvent
+ }
+
+ public SectionPos getLastSectionPos() {
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 85bd61bd45690c5532f56d8f11b81f7a11f3e284..05d2f3c26d10169f6cf43bcb6c48db5d27b5cbac 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -802,7 +802,7 @@ public abstract class PlayerList {
+ // CraftBukkit end
+ if (teleporttransition.missingRespawnBlock()) {
+ entityplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F));
+- entityplayer1.setRespawnPosition(null, null, 0f, false, false, PlayerSpawnChangeEvent.Cause.RESET); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed
++ entityplayer1.setRespawnPosition(null, null, 0f, false, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLAYER_RESPAWN); // CraftBukkit - SPIGOT-5988: Clear respawn location when obstructed // Paper - PlayerSetSpawnEvent
+ }
+
+ int i = flag ? 1 : 0;
+diff --git a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java
+index db26b5a0464bd6087eeacaf6dd61eba37365df92..9117c035d5a6ff114b028fad3380ceb1fc2b9691 100644
+--- a/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/RespawnAnchorBlock.java
+@@ -88,9 +88,14 @@ public class RespawnAnchorBlock extends Block {
+ ServerPlayer entityplayer = (ServerPlayer) player;
+
+ if (entityplayer.getRespawnDimension() != world.dimension() || !pos.equals(entityplayer.getRespawnPosition())) {
+- entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, org.bukkit.event.player.PlayerSpawnChangeEvent.Cause.RESPAWN_ANCHOR); // CraftBukkit
++ if (entityplayer.setRespawnPosition(world.dimension(), pos, 0.0F, false, true, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.RESPAWN_ANCHOR)) { // Paper - Add PlayerSetSpawnEvent
+ world.playSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, SoundEvents.RESPAWN_ANCHOR_SET_SPAWN, SoundSource.BLOCKS, 1.0F, 1.0F);
+ return InteractionResult.SUCCESS_SERVER;
++ // Paper start - Add PlayerSetSpawnEvent
++ } else {
++ return InteractionResult.FAIL;
++ }
++ // Paper end - Add PlayerSetSpawnEvent
+ }
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+index ff8d6a0ceb41258541c0049464dc0923ed4872bd..f363f885b3dc1852b09914f034740794e3025d3d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+@@ -1420,9 +1420,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+ @Override
+ public void setRespawnLocation(Location location, boolean override) {
+ if (location == null) {
+- this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, PlayerSpawnChangeEvent.Cause.PLUGIN);
++ this.getHandle().setRespawnPosition(null, null, 0.0F, override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - Add PlayerSetSpawnEvent
+ } else {
+- this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), CraftLocation.toBlockPosition(location), location.getYaw(), override, false, PlayerSpawnChangeEvent.Cause.PLUGIN);
++ this.getHandle().setRespawnPosition(((CraftWorld) location.getWorld()).getHandle().dimension(), CraftLocation.toBlockPosition(location), location.getYaw(), override, false, com.destroystokyo.paper.event.player.PlayerSetSpawnEvent.Cause.PLUGIN); // Paper - Add PlayerSetSpawnEvent
+ }
+ }
+
diff --git a/patches/server/0564-Make-hoppers-respect-inventory-max-stack-size.patch b/patches/server/0564-Make-hoppers-respect-inventory-max-stack-size.patch
new file mode 100644
index 0000000000..fc01c5cad4
--- /dev/null
+++ b/patches/server/0564-Make-hoppers-respect-inventory-max-stack-size.patch
@@ -0,0 +1,30 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 7 Jul 2021 16:30:17 -0700
+Subject: [PATCH] Make hoppers respect inventory max stack size
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+index 8913f434967457a16dd708252834ba001ada1a03..b35b44f0776aeb95ef0eda666ba0b652c5e90274 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+@@ -495,15 +495,17 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen
+
+ if (itemstack1.isEmpty()) {
+ // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem
++ ItemStack leftover = ItemStack.EMPTY; // Paper - Make hoppers respect inventory max stack size
+ if (!stack.isEmpty() && stack.getCount() > to.getMaxStackSize()) {
++ leftover = stack; // Paper - Make hoppers respect inventory max stack size
+ stack = stack.split(to.getMaxStackSize());
+ }
+ // Spigot end
+ to.setItem(slot, stack);
+- stack = ItemStack.EMPTY;
++ stack = leftover; // Paper - Make hoppers respect inventory max stack size
+ flag = true;
+ } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) {
+- int j = stack.getMaxStackSize() - itemstack1.getCount();
++ int j = Math.min(stack.getMaxStackSize(), to.getMaxStackSize()) - itemstack1.getCount(); // Paper - Make hoppers respect inventory max stack size
+ int k = Math.min(stack.getCount(), j);
+
+ stack.shrink(k);
diff --git a/patches/server/0565-Optimize-entity-tracker-passenger-checks.patch b/patches/server/0565-Optimize-entity-tracker-passenger-checks.patch
new file mode 100644
index 0000000000..e64be30cfc
--- /dev/null
+++ b/patches/server/0565-Optimize-entity-tracker-passenger-checks.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Andrew Steinborn <[email protected]>
+Date: Sun, 8 Aug 2021 00:52:54 -0400
+Subject: [PATCH] Optimize entity tracker passenger checks
+
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
+index 50f1e690d499940ce6c2174dbc609af21fd7b0cb..15968ea0b86a35fec8c0301bbdccc4ecd491197b 100644
+--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
+@@ -75,7 +75,7 @@ public class ServerEntity {
+ private Vec3 lastSentMovement;
+ private int tickCount;
+ private int teleportDelay;
+- private List<Entity> lastPassengers = Collections.emptyList();
++ private List<Entity> lastPassengers = com.google.common.collect.ImmutableList.of(); // Paper - optimize passenger checks
+ private boolean wasRiding;
+ private boolean wasOnGround;
+ @Nullable
diff --git a/patches/server/0566-Config-option-for-Piglins-guarding-chests.patch b/patches/server/0566-Config-option-for-Piglins-guarding-chests.patch
new file mode 100644
index 0000000000..32c61a7ec0
--- /dev/null
+++ b/patches/server/0566-Config-option-for-Piglins-guarding-chests.patch
@@ -0,0 +1,18 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Wed, 2 Dec 2020 03:07:58 -0800
+Subject: [PATCH] Config option for Piglins guarding chests
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
+index 55868c82bf8bd61ce3494aa9f363c20c88ec6aa6..4f3048615a34fc2c067e09aec76af94cde6a74e0 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/PiglinAi.java
+@@ -478,6 +478,7 @@ public class PiglinAi {
+ }
+
+ public static void angerNearbyPiglins(ServerLevel world, Player player, boolean blockOpen) {
++ if (!player.level().paperConfig().entities.behavior.piglinsGuardChests) return; // Paper - Config option for Piglins guarding chests
+ List<Piglin> list = player.level().getEntitiesOfClass(Piglin.class, player.getBoundingBox().inflate(16.0D));
+
+ list.stream().filter(PiglinAi::isIdle).filter((entitypiglin) -> {
diff --git a/patches/server/0567-Add-EntityDamageItemEvent.patch b/patches/server/0567-Add-EntityDamageItemEvent.patch
new file mode 100644
index 0000000000..bfe6c8751a
--- /dev/null
+++ b/patches/server/0567-Add-EntityDamageItemEvent.patch
@@ -0,0 +1,94 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Tue, 22 Dec 2020 13:52:48 -0800
+Subject: [PATCH] Add EntityDamageItemEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java
+index 225206a055e2f6bf4dbb18434cb3401d02746387..e320264c2283c2c09910ea70606413c73f443b1f 100644
+--- a/src/main/java/net/minecraft/world/item/ItemStack.java
++++ b/src/main/java/net/minecraft/world/item/ItemStack.java
+@@ -697,11 +697,11 @@ public final class ItemStack implements DataComponentHolder {
+ return this.isDamageableItem() && this.getDamageValue() >= this.getMaxDamage() - 1;
+ }
+
+- public void hurtAndBreak(int amount, ServerLevel world, @Nullable ServerPlayer player, Consumer<Item> breakCallback) {
++ public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer<Item> breakCallback) { // Paper - Add EntityDamageItemEvent
+ int j = this.processDurabilityChange(amount, world, player);
+ // CraftBukkit start
+- if (player != null) {
+- PlayerItemDamageEvent event = new PlayerItemDamageEvent(player.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j);
++ if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
++ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j); // Paper - Add EntityDamageItemEvent
+ event.getPlayer().getServer().getPluginManager().callEvent(event);
+
+ if (j != event.getDamage() || event.isCancelled()) {
+@@ -712,6 +712,14 @@ public final class ItemStack implements DataComponentHolder {
+ }
+
+ j = event.getDamage();
++ // Paper start - Add EntityDamageItemEvent
++ } else if (player != null) {
++ io.papermc.paper.event.entity.EntityDamageItemEvent event = new io.papermc.paper.event.entity.EntityDamageItemEvent(player.getBukkitLivingEntity(), CraftItemStack.asCraftMirror(this), amount);
++ if (!event.callEvent()) {
++ return;
++ }
++ j = event.getDamage();
++ // Paper end - Add EntityDamageItemEvent
+ }
+ // CraftBukkit end
+
+@@ -721,21 +729,21 @@ public final class ItemStack implements DataComponentHolder {
+
+ }
+
+- private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable ServerPlayer player) {
+- return !this.isDamageableItem() ? 0 : (player != null && player.hasInfiniteMaterials() ? 0 : (baseDamage > 0 ? EnchantmentHelper.processDurabilityChange(world, this, baseDamage) : baseDamage));
++ private int processDurabilityChange(int baseDamage, ServerLevel world, @Nullable LivingEntity player) { // Paper - Add EntityDamageItemEvent
++ return !this.isDamageableItem() ? 0 : (player instanceof ServerPlayer && player.hasInfiniteMaterials() ? 0 : (baseDamage > 0 ? EnchantmentHelper.processDurabilityChange(world, this, baseDamage) : baseDamage)); // Paper - Add EntityDamageItemEvent
+ }
+
+- private void applyDamage(int damage, @Nullable ServerPlayer player, Consumer<Item> breakCallback) {
+- if (player != null) {
+- CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(player, this, damage);
++ private void applyDamage(int damage, @Nullable LivingEntity player, Consumer<Item> breakCallback) { // Paper - Add EntityDamageItemEvent
++ if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
++ CriteriaTriggers.ITEM_DURABILITY_CHANGED.trigger(serverPlayer, this, damage); // Paper - Add EntityDamageItemEvent
+ }
+
+ this.setDamageValue(damage);
+ if (this.isBroken()) {
+ Item item = this.getItem();
+ // CraftBukkit start - Check for item breaking
+- if (this.count == 1 && player != null) {
+- org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(player, this);
++ if (this.count == 1 && player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
++ org.bukkit.craftbukkit.event.CraftEventFactory.callPlayerItemBreakEvent(serverPlayer, this); // Paper - Add EntityDamageItemEvent
+ }
+ // CraftBukkit end
+
+@@ -773,7 +781,7 @@ public final class ItemStack implements DataComponentHolder {
+ entityplayer = null;
+ }
+
+- this.hurtAndBreak(amount, worldserver, entityplayer, (item) -> {
++ this.hurtAndBreak(amount, worldserver, entity, (item) -> { // Paper - Add EntityDamageItemEvent
+ entity.onEquippedItemBroken(item, slot);
+ });
+ }
+diff --git a/src/main/java/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java b/src/main/java/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java
+index 0019c8548aa4901b248ced32cc1475ad14b725bf..7e21c8b10debd70c35a138c14b9b177ef6fff37a 100644
+--- a/src/main/java/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java
++++ b/src/main/java/net/minecraft/world/item/enchantment/effects/ChangeItemDamage.java
+@@ -21,9 +21,9 @@ public record ChangeItemDamage(LevelBasedValue amount) implements EnchantmentEnt
+ public void apply(ServerLevel world, int level, EnchantedItemInUse context, Entity user, Vec3 pos) {
+ ItemStack itemStack = context.itemStack();
+ if (itemStack.has(DataComponents.MAX_DAMAGE) && itemStack.has(DataComponents.DAMAGE)) {
+- ServerPlayer serverPlayer2 = context.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null;
++ // ServerPlayer serverPlayer2 = context.owner() instanceof ServerPlayer serverPlayer ? serverPlayer : null; // Paper - EntityDamageItemEvent - always pass in entity
+ int i = (int)this.amount.calculate(level);
+- itemStack.hurtAndBreak(i, world, serverPlayer2, context.onBreak());
++ itemStack.hurtAndBreak(i, world, context.owner(), context.onBreak()); // Paper - EntityDamageItemEvent - always pass in entity
+ }
+ }
+
diff --git a/patches/server/0568-Optimize-indirect-passenger-iteration.patch b/patches/server/0568-Optimize-indirect-passenger-iteration.patch
new file mode 100644
index 0000000000..91517284fb
--- /dev/null
+++ b/patches/server/0568-Optimize-indirect-passenger-iteration.patch
@@ -0,0 +1,53 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Andrew Steinborn <[email protected]>
+Date: Mon, 9 Aug 2021 00:38:37 -0400
+Subject: [PATCH] Optimize indirect passenger iteration
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index a18e716a8c1c6f1fe23dcd3f4da033da95417e22..ddb3d68f064d2dd82b42d0013c65f3685ef1065d 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -4118,20 +4118,34 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ }
+
+ private Stream<Entity> getIndirectPassengersStream() {
++ if (this.passengers.isEmpty()) { return Stream.of(); } // Paper - Optimize indirect passenger iteration
+ return this.passengers.stream().flatMap(Entity::getSelfAndPassengers);
+ }
+
+ @Override
+ public Stream<Entity> getSelfAndPassengers() {
++ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration
+ return Stream.concat(Stream.of(this), this.getIndirectPassengersStream());
+ }
+
+ @Override
+ public Stream<Entity> getPassengersAndSelf() {
++ if (this.passengers.isEmpty()) { return Stream.of(this); } // Paper - Optimize indirect passenger iteration
+ return Stream.concat(this.passengers.stream().flatMap(Entity::getPassengersAndSelf), Stream.of(this));
+ }
+
+ public Iterable<Entity> getIndirectPassengers() {
++ // Paper start - Optimize indirect passenger iteration
++ if (this.passengers.isEmpty()) { return ImmutableList.of(); }
++ ImmutableList.Builder<Entity> indirectPassengers = ImmutableList.builder();
++ for (Entity passenger : this.passengers) {
++ indirectPassengers.add(passenger);
++ indirectPassengers.addAll(passenger.getIndirectPassengers());
++ }
++ return indirectPassengers.build();
++ }
++ private Iterable<Entity> getIndirectPassengers_old() {
++ // Paper end - Optimize indirect passenger iteration
+ return () -> {
+ return this.getIndirectPassengersStream().iterator();
+ };
+@@ -4144,6 +4158,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ }
+
+ public boolean hasExactlyOnePlayerPassenger() {
++ if (this.passengers.isEmpty()) { return false; } // Paper - Optimize indirect passenger iteration
+ return this.countPlayerPassengers() == 1;
+ }
+
diff --git a/patches/server/0569-Configurable-item-frame-map-cursor-update-interval.patch b/patches/server/0569-Configurable-item-frame-map-cursor-update-interval.patch
new file mode 100644
index 0000000000..03aaddc9be
--- /dev/null
+++ b/patches/server/0569-Configurable-item-frame-map-cursor-update-interval.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Warrior <[email protected]>
+Date: Fri, 13 Aug 2021 01:14:38 +0200
+Subject: [PATCH] Configurable item frame map cursor update interval
+
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
+index 15968ea0b86a35fec8c0301bbdccc4ecd491197b..78d3aa089d990190cdf5cd7ac14410909235f133 100644
+--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
++++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
+@@ -120,7 +120,7 @@ public class ServerEntity {
+ if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block
+ ItemStack itemstack = entityitemframe.getItem();
+
+- if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks
++ if (this.level.paperConfig().maps.itemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig().maps.itemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable
+ MapId mapid = (MapId) itemstack.get(DataComponents.MAP_ID);
+ MapItemSavedData worldmap = MapItem.getSavedData(mapid, this.level);
+
diff --git a/patches/server/0570-Change-EnderEye-target-without-changing-other-things.patch b/patches/server/0570-Change-EnderEye-target-without-changing-other-things.patch
new file mode 100644
index 0000000000..5612422ea7
--- /dev/null
+++ b/patches/server/0570-Change-EnderEye-target-without-changing-other-things.patch
@@ -0,0 +1,54 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 21 Aug 2021 12:13:53 -0700
+Subject: [PATCH] Change EnderEye target without changing other things
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java
+index 573b96c9e0c89860c3da031c5aa239f6a7ad0c6e..fd1f5de7dc151dfd187d23e022b2c5435ed8accc 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/EyeOfEnder.java
+@@ -76,6 +76,11 @@ public class EyeOfEnder extends Entity implements ItemSupplier {
+ }
+
+ public void signalTo(BlockPos pos) {
++ // Paper start - Change EnderEye target without changing other things
++ this.signalTo(pos, true);
++ }
++ public void signalTo(BlockPos pos, boolean update) {
++ // Paper end - Change EnderEye target without changing other things
+ double d0 = (double) pos.getX();
+ int i = pos.getY();
+ double d1 = (double) pos.getZ();
+@@ -93,8 +98,10 @@ public class EyeOfEnder extends Entity implements ItemSupplier {
+ this.tz = d1;
+ }
+
++ if (update) { // Paper - Change EnderEye target without changing other things
+ this.life = 0;
+ this.surviveAfterDeath = this.random.nextInt(5) > 0;
++ } // Paper - Change EnderEye target without changing other things
+ }
+
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java
+index d4dfc7a0266086b9bf2c3966c6e149453d0583ba..27f56fa4b7ef92a9a4dfa6b782350424b88210f2 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderSignal.java
+@@ -32,8 +32,15 @@ public class CraftEnderSignal extends CraftEntity implements EnderSignal {
+
+ @Override
+ public void setTargetLocation(Location location) {
++ // Paper start - Change EnderEye target without changing other things
++ this.setTargetLocation(location, true);
++ }
++
++ @Override
++ public void setTargetLocation(Location location, boolean update) {
++ // Paper end - Change EnderEye target without changing other things
+ Preconditions.checkArgument(this.getWorld().equals(location.getWorld()), "Cannot target EnderSignal across worlds");
+- this.getHandle().signalTo(CraftLocation.toBlockPosition(location));
++ this.getHandle().signalTo(CraftLocation.toBlockPosition(location), update); // Paper - Change EnderEye target without changing other things
+ }
+
+ @Override
diff --git a/patches/server/0571-Add-BlockBreakBlockEvent.patch b/patches/server/0571-Add-BlockBreakBlockEvent.patch
new file mode 100644
index 0000000000..36b29ddf20
--- /dev/null
+++ b/patches/server/0571-Add-BlockBreakBlockEvent.patch
@@ -0,0 +1,87 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 3 Jan 2021 17:58:11 -0800
+Subject: [PATCH] Add BlockBreakBlockEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java
+index 533df8ff84ec4224637dfb0837104a6db1ea9901..3c7e373eb1601670d923e6ffa46817ca53e321db 100644
+--- a/src/main/java/net/minecraft/world/level/block/Block.java
++++ b/src/main/java/net/minecraft/world/level/block/Block.java
+@@ -295,6 +295,24 @@ public class Block extends BlockBehaviour implements ItemLike {
+
+ }
+
++ // Paper start - Add BlockBreakBlockEvent
++ public static boolean dropResources(BlockState state, LevelAccessor levelAccessor, BlockPos pos, @Nullable BlockEntity blockEntity, BlockPos source) {
++ if (levelAccessor instanceof ServerLevel serverLevel) {
++ List<org.bukkit.inventory.ItemStack> items = new java.util.ArrayList<>();
++ for (ItemStack drop : Block.getDrops(state, serverLevel, pos, blockEntity)) {
++ items.add(org.bukkit.craftbukkit.inventory.CraftItemStack.asBukkitCopy(drop));
++ }
++ io.papermc.paper.event.block.BlockBreakBlockEvent event = new io.papermc.paper.event.block.BlockBreakBlockEvent(org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, pos), org.bukkit.craftbukkit.block.CraftBlock.at(levelAccessor, source), items);
++ event.callEvent();
++ for (org.bukkit.inventory.ItemStack drop : event.getDrops()) {
++ popResource(serverLevel, pos, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(drop));
++ }
++ state.spawnAfterBreak(serverLevel, pos, ItemStack.EMPTY, true);
++ }
++ return true;
++ }
++ // Paper end - Add BlockBreakBlockEvent
++
+ public static void dropResources(BlockState state, Level world, BlockPos pos, @Nullable BlockEntity blockEntity, @Nullable Entity entity, ItemStack tool) {
+ if (world instanceof ServerLevel) {
+ Block.getDrops(state, (ServerLevel) world, pos, blockEntity, entity, tool).forEach((itemstack1) -> {
+diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
+index 0c6b517196d48ba4384eac240b7e580adfdbc4d4..4973d75b26880e39d42b5ef533896f43a1f07cba 100644
+--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
+@@ -406,7 +406,7 @@ public class PistonBaseBlock extends DirectionalBlock {
+ iblockdata1 = world.getBlockState(blockposition3);
+ BlockEntity tileentity = iblockdata1.hasBlockEntity() ? world.getBlockEntity(blockposition3) : null;
+
+- dropResources(iblockdata1, world, blockposition3, tileentity);
++ dropResources(iblockdata1, world, blockposition3, tileentity, pos); // Paper - Add BlockBreakBlockEvent
+ world.setBlock(blockposition3, Blocks.AIR.defaultBlockState(), 18);
+ world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, blockposition3, GameEvent.Context.of(iblockdata1));
+ if (!iblockdata1.is(BlockTags.FIRE)) {
+diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
+index 83dc8bcd9e2b8ecbd32225e4e10aec392ef28325..261e5994d13f8bc30490b86691c80c0a21e7640a 100644
+--- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
++++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java
+@@ -316,7 +316,7 @@ public abstract class FlowingFluid extends Fluid {
+ ifluidcontainer.placeLiquid(world, pos, state, fluidState);
+ } else {
+ if (!state.isAir()) {
+- this.beforeDestroyingBlock(world, pos, state);
++ this.beforeDestroyingBlock(world, pos, state, pos.relative(direction.getOpposite())); // Paper - Add BlockBreakBlockEvent
+ }
+
+ world.setBlock(pos, fluidState.createLegacyBlock(), 3);
+@@ -324,6 +324,7 @@ public abstract class FlowingFluid extends Fluid {
+
+ }
+
++ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) { beforeDestroyingBlock(world, pos, state); } // Paper - Add BlockBreakBlockEvent
+ protected abstract void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state);
+
+ protected int getSlopeDistance(LevelReader world, BlockPos pos, int i, Direction direction, BlockState state, FlowingFluid.SpreadContext spreadCache) {
+diff --git a/src/main/java/net/minecraft/world/level/material/WaterFluid.java b/src/main/java/net/minecraft/world/level/material/WaterFluid.java
+index 421f5d9a57d87a87a801213d562ad5fe244e7b65..0a7c05c08bf8c6c331b91e399dc4103a91dc20fe 100644
+--- a/src/main/java/net/minecraft/world/level/material/WaterFluid.java
++++ b/src/main/java/net/minecraft/world/level/material/WaterFluid.java
+@@ -81,6 +81,13 @@ public abstract class WaterFluid extends FlowingFluid {
+ return world.getGameRules().getBoolean(GameRules.RULE_WATER_SOURCE_CONVERSION);
+ }
+
++ // Paper start - Add BlockBreakBlockEvent
++ @Override
++ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state, BlockPos source) {
++ BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null;
++ Block.dropResources(state, world, pos, tileentity, source);
++ }
++ // Paper end - Add BlockBreakBlockEvent
+ @Override
+ protected void beforeDestroyingBlock(LevelAccessor world, BlockPos pos, BlockState state) {
+ BlockEntity blockEntity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null;
diff --git a/patches/server/0572-Option-to-prevent-data-components-copy-in-smithing-r.patch b/patches/server/0572-Option-to-prevent-data-components-copy-in-smithing-r.patch
new file mode 100644
index 0000000000..1c9a4f5da5
--- /dev/null
+++ b/patches/server/0572-Option-to-prevent-data-components-copy-in-smithing-r.patch
@@ -0,0 +1,157 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 26 Sep 2021 12:57:28 -0700
+Subject: [PATCH] Option to prevent data components copy in smithing recipes
+
+
+diff --git a/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java b/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
+index fa003ce16020eaab554bfd833ace779c8cefc617..017748b26590364d846a5cddaa3490b2293ad276 100644
+--- a/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
++++ b/src/main/java/net/minecraft/world/item/crafting/SmithingTransformRecipe.java
+@@ -30,8 +30,15 @@ public class SmithingTransformRecipe implements SmithingRecipe {
+ final ItemStack result;
+ @Nullable
+ private PlacementInfo placementInfo;
++ final boolean copyDataComponents; // Paper - Option to prevent data components copy
+
+ public SmithingTransformRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition, ItemStack result) {
++ // Paper start - Option to prevent data components copy
++ this(template, base, addition, result, true);
++ }
++ public SmithingTransformRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition, ItemStack result, boolean copyDataComponents) {
++ this.copyDataComponents = copyDataComponents;
++ // Paper end - Option to prevent data components copy
+ this.template = template;
+ this.base = base;
+ this.addition = addition;
+@@ -41,7 +48,9 @@ public class SmithingTransformRecipe implements SmithingRecipe {
+ public ItemStack assemble(SmithingRecipeInput input, HolderLookup.Provider registries) {
+ ItemStack itemstack = input.base().transmuteCopy(this.result.getItem(), this.result.getCount());
+
++ if (this.copyDataComponents) { // Paper - Option to prevent data components copy
+ itemstack.applyComponents(this.result.getComponentsPatch());
++ } // Paper - Option to prevent data components copy
+ return itemstack;
+ }
+
+@@ -84,7 +93,7 @@ public class SmithingTransformRecipe implements SmithingRecipe {
+ public Recipe toBukkitRecipe(NamespacedKey id) {
+ CraftItemStack result = CraftItemStack.asCraftMirror(this.result);
+
+- CraftSmithingTransformRecipe recipe = new CraftSmithingTransformRecipe(id, result, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition));
++ CraftSmithingTransformRecipe recipe = new CraftSmithingTransformRecipe(id, result, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy
+
+ return recipe;
+ }
+diff --git a/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java b/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
+index a5adce474b2e78233c39cbed367e3d14515923b1..7209170454f10225d7d4a4a107e6717fc18a02ad 100644
+--- a/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
++++ b/src/main/java/net/minecraft/world/item/crafting/SmithingTrimRecipe.java
+@@ -35,18 +35,28 @@ public class SmithingTrimRecipe implements SmithingRecipe {
+ final Optional<Ingredient> addition;
+ @Nullable
+ private PlacementInfo placementInfo;
++ final boolean copyDataComponents; // Paper - Option to prevent data components copy
+
+ public SmithingTrimRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition) {
++ // Paper start - Option to prevent data components copy
++ this(template, base, addition, true);
++ }
++ public SmithingTrimRecipe(Optional<Ingredient> template, Optional<Ingredient> base, Optional<Ingredient> addition, boolean copyDataComponents) {
++ this.copyDataComponents = copyDataComponents;
++ // Paper end - Option to prevent data components copy
+ this.template = template;
+ this.base = base;
+ this.addition = addition;
+ }
+
+ public ItemStack assemble(SmithingRecipeInput input, HolderLookup.Provider registries) {
+- return SmithingTrimRecipe.applyTrim(registries, input.base(), input.addition(), input.template());
++ return SmithingTrimRecipe.applyTrim(registries, input.base(), input.addition(), input.template(), this.copyDataComponents);
+ }
+
+ public static ItemStack applyTrim(HolderLookup.Provider registries, ItemStack base, ItemStack addition, ItemStack template) {
++ return applyTrim(registries, base, addition, template, true);
++ }
++ public static ItemStack applyTrim(HolderLookup.Provider registries, ItemStack base, ItemStack addition, ItemStack template, boolean copyDataComponents) {
+ Optional<Holder.Reference<TrimMaterial>> optional = TrimMaterials.getFromIngredient(registries, addition);
+ Optional<Holder.Reference<TrimPattern>> optional1 = TrimPatterns.getFromTemplate(registries, template);
+
+@@ -56,7 +66,7 @@ public class SmithingTrimRecipe implements SmithingRecipe {
+ if (armortrim != null && armortrim.hasPatternAndMaterial((Holder) optional1.get(), (Holder) optional.get())) {
+ return ItemStack.EMPTY;
+ } else {
+- ItemStack itemstack3 = base.copyWithCount(1);
++ ItemStack itemstack3 = copyDataComponents ? base.copyWithCount(1) : new ItemStack(base.getItem(), 1); // Paper - Option to prevent data components copy
+
+ itemstack3.set(DataComponents.TRIM, new ArmorTrim((Holder) optional.get(), (Holder) optional1.get()));
+ return itemstack3;
+@@ -107,7 +117,7 @@ public class SmithingTrimRecipe implements SmithingRecipe {
+ // CraftBukkit start
+ @Override
+ public Recipe toBukkitRecipe(NamespacedKey id) {
+- return new CraftSmithingTrimRecipe(id, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition));
++ return new CraftSmithingTrimRecipe(id, CraftRecipe.toBukkit(this.template), CraftRecipe.toBukkit(this.base), CraftRecipe.toBukkit(this.addition), this.copyDataComponents); // Paper - Option to prevent data components copy
+ }
+ // CraftBukkit end
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java
+index 0dc2be8f502a50f13d8fe860c209ebfa43a931ea..af6c1ccdf2b91b1284daee5552eb44cc9a34cd5f 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTransformRecipe.java
+@@ -11,12 +11,17 @@ public class CraftSmithingTransformRecipe extends SmithingTransformRecipe implem
+ public CraftSmithingTransformRecipe(NamespacedKey key, ItemStack result, RecipeChoice template, RecipeChoice base, RecipeChoice addition) {
+ super(key, result, template, base, addition);
+ }
++ // Paper start - Option to prevent data components copy
++ public CraftSmithingTransformRecipe(NamespacedKey key, ItemStack result, RecipeChoice template, RecipeChoice base, RecipeChoice addition, boolean copyDataComponents) {
++ super(key, result, template, base, addition, copyDataComponents);
++ }
++ // Paper end - Option to prevent data components copy
+
+ public static CraftSmithingTransformRecipe fromBukkitRecipe(SmithingTransformRecipe recipe) {
+ if (recipe instanceof CraftSmithingTransformRecipe) {
+ return (CraftSmithingTransformRecipe) recipe;
+ }
+- CraftSmithingTransformRecipe ret = new CraftSmithingTransformRecipe(recipe.getKey(), recipe.getResult(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition());
++ CraftSmithingTransformRecipe ret = new CraftSmithingTransformRecipe(recipe.getKey(), recipe.getResult(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition(), recipe.willCopyDataComponents()); // Paper - Option to prevent data components copy
+ return ret;
+ }
+
+@@ -24,6 +29,6 @@ public class CraftSmithingTransformRecipe extends SmithingTransformRecipe implem
+ public void addToCraftingManager() {
+ ItemStack result = this.getResult();
+
+- MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTransformRecipe(this.toNMSOptional(this.getTemplate(), false), this.toNMSOptional(this.getBase(), false), this.toNMSOptional(this.getAddition(), false), CraftItemStack.asNMSCopy(result))));
++ MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTransformRecipe(this.toNMSOptional(this.getTemplate(), false), this.toNMSOptional(this.getBase(), false), this.toNMSOptional(this.getAddition(), false), CraftItemStack.asNMSCopy(result), this.willCopyDataComponents()))); // Paper - Option to prevent data components copy
+ }
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java
+index 202963e2f53b5e7d6fd43c58b27ad49ce009cc3c..fb710aa6dc416a3423345ad5b6e9494507eb0cb4 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftSmithingTrimRecipe.java
+@@ -11,17 +11,22 @@ public class CraftSmithingTrimRecipe extends SmithingTrimRecipe implements Craft
+ public CraftSmithingTrimRecipe(NamespacedKey key, RecipeChoice template, RecipeChoice base, RecipeChoice addition) {
+ super(key, template, base, addition);
+ }
++ // Paper start - Option to prevent data components copy
++ public CraftSmithingTrimRecipe(NamespacedKey key, RecipeChoice template, RecipeChoice base, RecipeChoice addition, boolean copyDataComponents) {
++ super(key, template, base, addition, copyDataComponents);
++ }
++ // Paper end - Option to prevent data components copy
+
+ public static CraftSmithingTrimRecipe fromBukkitRecipe(SmithingTrimRecipe recipe) {
+ if (recipe instanceof CraftSmithingTrimRecipe) {
+ return (CraftSmithingTrimRecipe) recipe;
+ }
+- CraftSmithingTrimRecipe ret = new CraftSmithingTrimRecipe(recipe.getKey(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition());
++ CraftSmithingTrimRecipe ret = new CraftSmithingTrimRecipe(recipe.getKey(), recipe.getTemplate(), recipe.getBase(), recipe.getAddition(), recipe.willCopyDataComponents()); // Paper - Option to prevent data components copy
+ return ret;
+ }
+
+ @Override
+ public void addToCraftingManager() {
+- MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTrimRecipe(this.toNMSOptional(this.getTemplate(), false), this.toNMSOptional(this.getBase(), false), this.toNMSOptional(this.getAddition(), false))));
++ MinecraftServer.getServer().getRecipeManager().addRecipe(new RecipeHolder<>(CraftRecipe.toMinecraft(this.getKey()), new net.minecraft.world.item.crafting.SmithingTrimRecipe(this.toNMSOptional(this.getTemplate(), false), this.toNMSOptional(this.getBase(), false), this.toNMSOptional(this.getAddition(), false), this.willCopyDataComponents()))); // Paper - Option to prevent data components copy
+ }
+ }
diff --git a/patches/server/0573-More-CommandBlock-API.patch b/patches/server/0573-More-CommandBlock-API.patch
new file mode 100644
index 0000000000..5fa4fa81e1
--- /dev/null
+++ b/patches/server/0573-More-CommandBlock-API.patch
@@ -0,0 +1,101 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 23 Sep 2021 10:40:09 -0700
+Subject: [PATCH] More CommandBlock API
+
+
+diff --git a/src/main/java/io/papermc/paper/commands/PaperCommandBlockHolder.java b/src/main/java/io/papermc/paper/commands/PaperCommandBlockHolder.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..0463aef1c8be238b00e73907bde927f1522a46ce
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/commands/PaperCommandBlockHolder.java
+@@ -0,0 +1,34 @@
++package io.papermc.paper.commands;
++
++import io.papermc.paper.adventure.PaperAdventure;
++import io.papermc.paper.command.CommandBlockHolder;
++import net.kyori.adventure.text.Component;
++import net.minecraft.world.level.BaseCommandBlock;
++import org.jspecify.annotations.NullMarked;
++import org.jspecify.annotations.Nullable;
++
++@NullMarked
++public interface PaperCommandBlockHolder extends CommandBlockHolder {
++
++ BaseCommandBlock getCommandBlockHandle();
++
++ @Override
++ default Component lastOutput() {
++ return PaperAdventure.asAdventure(this.getCommandBlockHandle().getLastOutput());
++ }
++
++ @Override
++ default void lastOutput(final @Nullable Component lastOutput) {
++ this.getCommandBlockHandle().setLastOutput(PaperAdventure.asVanilla(lastOutput));
++ }
++
++ @Override
++ default int getSuccessCount() {
++ return this.getCommandBlockHandle().getSuccessCount();
++ }
++
++ @Override
++ default void setSuccessCount(final int successCount) {
++ this.getCommandBlockHandle().setSuccessCount(successCount);
++ }
++}
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java
+index f5b0bec4c1164fe7ef6da1f19a6ce9bb3d6864d0..138e6539a7786ded482a24aa88a367da7beaabf9 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCommandBlock.java
+@@ -6,7 +6,7 @@ import org.bukkit.World;
+ import org.bukkit.block.CommandBlock;
+ import org.bukkit.craftbukkit.util.CraftChatMessage;
+
+-public class CraftCommandBlock extends CraftBlockEntityState<CommandBlockEntity> implements CommandBlock {
++public class CraftCommandBlock extends CraftBlockEntityState<CommandBlockEntity> implements CommandBlock, io.papermc.paper.commands.PaperCommandBlockHolder {
+
+ public CraftCommandBlock(World world, CommandBlockEntity tileEntity) {
+ super(world, tileEntity);
+@@ -56,5 +56,10 @@ public class CraftCommandBlock extends CraftBlockEntityState<CommandBlockEntity>
+ public void name(net.kyori.adventure.text.Component name) {
+ getSnapshot().getCommandBlock().setCustomName(name == null ? net.minecraft.network.chat.Component.literal("@") : io.papermc.paper.adventure.PaperAdventure.asVanilla(name));
+ }
++
++ @Override
++ public net.minecraft.world.level.BaseCommandBlock getCommandBlockHandle() {
++ return getSnapshot().getCommandBlock();
++ }
+ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java
+index 9ea1537408ff2d790747b6e5a681d9171a4233ae..f34fa6715e477936097367a7aefd1a2bf87d3d90 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartCommand.java
+@@ -13,7 +13,7 @@ import org.bukkit.permissions.PermissionAttachment;
+ import org.bukkit.permissions.PermissionAttachmentInfo;
+ import org.bukkit.plugin.Plugin;
+
+-public class CraftMinecartCommand extends CraftMinecart implements CommandMinecart {
++public class CraftMinecartCommand extends CraftMinecart implements CommandMinecart, io.papermc.paper.commands.PaperCommandBlockHolder {
+ private final PermissibleBase perm = new PermissibleBase(this);
+
+ public CraftMinecartCommand(CraftServer server, MinecartCommandBlock entity) {
+@@ -64,6 +64,17 @@ public class CraftMinecartCommand extends CraftMinecart implements CommandMineca
+ public [email protected] Component name() {
+ return io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getHandle().getCommandBlock().getName());
+ }
++
++ @Override
++ public net.minecraft.world.level.BaseCommandBlock getCommandBlockHandle() {
++ return getHandle().getCommandBlock();
++ }
++
++ @Override
++ public void lastOutput(net.kyori.adventure.text.Component lastOutput) {
++ io.papermc.paper.commands.PaperCommandBlockHolder.super.lastOutput(lastOutput);
++ getCommandBlockHandle().onUpdated();
++ }
+ // Paper end
+
+ @Override
diff --git a/patches/server/0574-Add-missing-team-sidebar-display-slots.patch b/patches/server/0574-Add-missing-team-sidebar-display-slots.patch
new file mode 100644
index 0000000000..de7d3fc8e5
--- /dev/null
+++ b/patches/server/0574-Add-missing-team-sidebar-display-slots.patch
@@ -0,0 +1,114 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Fri, 1 Oct 2021 08:04:39 -0700
+Subject: [PATCH] Add missing team sidebar display slots
+
+== AT ==
+public org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations
+public org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations toBukkitSlot(Lnet/minecraft/world/scores/DisplaySlot;)Lorg/bukkit/scoreboard/DisplaySlot;
+public org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations fromBukkitSlot(Lorg/bukkit/scoreboard/DisplaySlot;)Lnet/minecraft/world/scores/DisplaySlot;
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java
+index 12fe2f8d0dcb715545e071023490a32125b9c4a4..fe29c08270854d37a4b111f66ebf261260200f28 100644
+--- a/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java
++++ b/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java
+@@ -35,6 +35,7 @@ public class FieldRename {
+ }
+
+ return switch (owner) {
++ case "org/bukkit/scoreboard/DisplaySlot" -> FieldRename.convertDisplaySlot(from); // Paper - DisplaySlot
+ case "org/bukkit/block/banner/PatternType" -> FieldRename.convertPatternTypeName(apiVersion, from);
+ case "org/bukkit/enchantments/Enchantment" -> FieldRename.convertEnchantmentName(apiVersion, from);
+ case "org/bukkit/block/Biome" -> FieldRename.convertBiomeName(apiVersion, from);
+@@ -60,6 +61,16 @@ public class FieldRename {
+ //}
+ // Paper end
+
++ // Paper start - DisplaySlot
++ @DoNotReroute
++ public static String convertDisplaySlot(final String from) {
++ if (from.startsWith("SIDEBAR_") && !from.startsWith("SIDEBAR_TEAM_")) {
++ return from.replace("SIDEBAR_", "SIDEBAR_TEAM_");
++ }
++ return from;
++ }
++ // Paper end - DisplaySlot
++
+ // PatternType
+ private static final FieldRenameData PATTERN_TYPE_DATA = FieldRenameData.Builder.newBuilder()
+ .forVersionsBefore(ApiVersion.FIELD_NAME_PARITY)
+diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java
+index 73c5ffff70605b32188a9bb5fb6c0ee04cb66efe..711d227f5ee6d63356a94a0567968da48e9f284c 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java
++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardTranslations.java
+@@ -7,35 +7,22 @@ import org.bukkit.scoreboard.RenderType;
+
+ public final class CraftScoreboardTranslations {
+ static final int MAX_DISPLAY_SLOT = 19;
++ @Deprecated // Paper
+ static final ImmutableBiMap<DisplaySlot, String> SLOTS = ImmutableBiMap.<DisplaySlot, String>builder()
+ .put(DisplaySlot.BELOW_NAME, "below_name")
+ .put(DisplaySlot.PLAYER_LIST, "list")
+ .put(DisplaySlot.SIDEBAR, "sidebar")
+- .put(DisplaySlot.SIDEBAR_BLACK, "sidebar.team.black")
+- .put(DisplaySlot.SIDEBAR_DARK_BLUE, "sidebar.team.dark_blue")
+- .put(DisplaySlot.SIDEBAR_DARK_GREEN, "sidebar.team.dark_green")
+- .put(DisplaySlot.SIDEBAR_DARK_AQUA, "sidebar.team.dark_aqua")
+- .put(DisplaySlot.SIDEBAR_DARK_RED, "sidebar.team.dark_red")
+- .put(DisplaySlot.SIDEBAR_DARK_PURPLE, "sidebar.team.dark_purple")
+- .put(DisplaySlot.SIDEBAR_GOLD, "sidebar.team.gold")
+- .put(DisplaySlot.SIDEBAR_GRAY, "sidebar.team.gray")
+- .put(DisplaySlot.SIDEBAR_DARK_GRAY, "sidebar.team.dark_gray")
+- .put(DisplaySlot.SIDEBAR_BLUE, "sidebar.team.blue")
+- .put(DisplaySlot.SIDEBAR_GREEN, "sidebar.team.green")
+- .put(DisplaySlot.SIDEBAR_AQUA, "sidebar.team.aqua")
+- .put(DisplaySlot.SIDEBAR_RED, "sidebar.team.red")
+- .put(DisplaySlot.SIDEBAR_LIGHT_PURPLE, "sidebar.team.light_purple")
+- .put(DisplaySlot.SIDEBAR_YELLOW, "sidebar.team.yellow")
+- .put(DisplaySlot.SIDEBAR_WHITE, "sidebar.team.white")
+ .buildOrThrow();
+
+ private CraftScoreboardTranslations() {}
+
+ public static DisplaySlot toBukkitSlot(net.minecraft.world.scores.DisplaySlot minecraft) {
++ if (true) return DisplaySlot.NAMES.value(minecraft.getSerializedName()); // Paper
+ return CraftScoreboardTranslations.SLOTS.inverse().get(minecraft.getSerializedName());
+ }
+
+ public static net.minecraft.world.scores.DisplaySlot fromBukkitSlot(DisplaySlot slot) {
++ if (true) return net.minecraft.world.scores.DisplaySlot.CODEC.byName(slot.getId()); // Paper
+ return net.minecraft.world.scores.DisplaySlot.CODEC.byName(CraftScoreboardTranslations.SLOTS.get(slot));
+ }
+
+diff --git a/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java b/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..345c96bfb6c559b41c2b6682067198a74d35b440
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/scoreboard/DisplaySlotTest.java
+@@ -0,0 +1,26 @@
++package io.papermc.paper.scoreboard;
++
++import org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations;
++import org.bukkit.scoreboard.DisplaySlot;
++import org.bukkit.support.environment.Normal;
++import org.junit.jupiter.api.Test;
++
++import static org.junit.jupiter.api.Assertions.assertNotNull;
++
++@Normal
++public class DisplaySlotTest {
++
++ @Test
++ public void testBukkitToMinecraftDisplaySlots() {
++ for (DisplaySlot bukkitSlot : DisplaySlot.values()) {
++ assertNotNull(CraftScoreboardTranslations.fromBukkitSlot(bukkitSlot));
++ }
++ }
++
++ @Test
++ public void testMinecraftToBukkitDisplaySlots() {
++ for (net.minecraft.world.scores.DisplaySlot nmsSlot : net.minecraft.world.scores.DisplaySlot.values()) {
++ assertNotNull(CraftScoreboardTranslations.toBukkitSlot(nmsSlot));
++ }
++ }
++}
diff --git a/patches/server/0575-Add-back-EntityPortalExitEvent.patch b/patches/server/0575-Add-back-EntityPortalExitEvent.patch
new file mode 100644
index 0000000000..d9d52b0184
--- /dev/null
+++ b/patches/server/0575-Add-back-EntityPortalExitEvent.patch
@@ -0,0 +1,48 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 16 May 2021 09:39:46 -0700
+Subject: [PATCH] Add back EntityPortalExitEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index ddb3d68f064d2dd82b42d0013c65f3685ef1065d..f9e52b8794d62cc79c420008a1a95707efa14321 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -3522,6 +3522,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ if (!this.isRemoved()) {
+ // CraftBukkit start
+ PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this), PositionMoveRotation.of(teleportTarget), teleportTarget.relatives());
++ Vec3 velocity = absolutePosition.deltaMovement(); // Paper
+ Location to = CraftLocation.toBukkit(absolutePosition.position(), teleportTarget.newLevel().getWorld(), absolutePosition.yRot(), absolutePosition.xRot());
+ // Paper start - gateway-specific teleport event
+ final EntityTeleportEvent teleEvent;
+@@ -3538,7 +3539,29 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ if (!to.equals(teleEvent.getTo())) {
+ to = teleEvent.getTo();
+ teleportTarget = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause());
++ // Paper start - Call EntityPortalExitEvent
++ velocity = Vec3.ZERO;
+ }
++ if (this.portalProcess != null) { // if in a portal
++ CraftEntity bukkitEntity = this.getBukkitEntity();
++ org.bukkit.event.entity.EntityPortalExitEvent event = new org.bukkit.event.entity.EntityPortalExitEvent(
++ bukkitEntity,
++ bukkitEntity.getLocation(), to.clone(),
++ bukkitEntity.getVelocity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(velocity)
++ );
++ event.callEvent();
++
++ // Only change the target if actually needed, since we reset relative flags
++ if (!event.isCancelled() && event.getTo() != null && (!event.getTo().equals(event.getFrom()) || !event.getAfter().equals(event.getBefore()))) {
++ to = event.getTo().clone();
++ velocity = org.bukkit.craftbukkit.util.CraftVector.toNMS(event.getAfter());
++ teleportTarget = new TeleportTransition(((CraftWorld) to.getWorld()).getHandle(), CraftLocation.toVec3D(to), velocity, to.getYaw(), to.getPitch(), teleportTarget.missingRespawnBlock(), teleportTarget.asPassenger(), Set.of(), teleportTarget.postTeleportTransition(), teleportTarget.cause());
++ }
++ }
++ if (this.isRemoved()) {
++ return null;
++ }
++ // Paper end - Call EntityPortalExitEvent
+ // CraftBukkit end
+ ServerLevel worldserver1 = teleportTarget.newLevel();
+ boolean flag = worldserver1.dimension() != worldserver.dimension();
diff --git a/patches/server/0576-Add-methods-to-find-targets-for-lightning-strikes.patch b/patches/server/0576-Add-methods-to-find-targets-for-lightning-strikes.patch
new file mode 100644
index 0000000000..a21e15c6fc
--- /dev/null
+++ b/patches/server/0576-Add-methods-to-find-targets-for-lightning-strikes.patch
@@ -0,0 +1,60 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jakub Zacek <[email protected]>
+Date: Mon, 4 Oct 2021 10:16:44 +0200
+Subject: [PATCH] Add methods to find targets for lightning strikes
+
+== AT ==
+public net.minecraft.server.level.ServerLevel findLightningRod(Lnet/minecraft/core/BlockPos;)Ljava/util/Optional;
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index 93656336af194994f59072fa89bfc338d89d76af..e6ff03143b3639f375323393814b1256b98687ad 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -741,6 +741,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ }
+
+ protected BlockPos findLightningTargetAround(BlockPos pos) {
++ // Paper start - Add methods to find targets for lightning strikes
++ return this.findLightningTargetAround(pos, false);
++ }
++ public BlockPos findLightningTargetAround(BlockPos pos, boolean returnNullWhenNoTarget) {
++ // Paper end - Add methods to find targets for lightning strikes
+ BlockPos blockposition1 = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos);
+ Optional<BlockPos> optional = this.findLightningRod(blockposition1);
+
+@@ -755,6 +760,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ if (!list.isEmpty()) {
+ return ((LivingEntity) list.get(this.random.nextInt(list.size()))).blockPosition();
+ } else {
++ if (returnNullWhenNoTarget) return null; // Paper - Add methods to find targets for lightning strikes
+ if (blockposition1.getY() == this.getMinY() - 1) {
+ blockposition1 = blockposition1.above(2);
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index d72ceb866f8632a8daa7dc19acdc57b6b78fd906..d650822155f4628ea9d61ecbd4208520746aa59f 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -676,6 +676,23 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+ return (LightningStrike) lightning.getBukkitEntity();
+ }
+
++ // Paper start - Add methods to find targets for lightning strikes
++ @Override
++ public Location findLightningRod(Location location) {
++ return this.world.findLightningRod(io.papermc.paper.util.MCUtil.toBlockPosition(location))
++ .map(blockPos -> io.papermc.paper.util.MCUtil.toLocation(this.world, blockPos)
++ // get the actual rod pos
++ .subtract(0, 1, 0))
++ .orElse(null);
++ }
++
++ @Override
++ public Location findLightningTarget(Location location) {
++ final BlockPos pos = this.world.findLightningTargetAround(io.papermc.paper.util.MCUtil.toBlockPosition(location), true);
++ return pos == null ? null : io.papermc.paper.util.MCUtil.toLocation(this.world, pos);
++ }
++ // Paper end - Add methods to find targets for lightning strikes
++
+ @Override
+ public boolean generateTree(Location loc, TreeType type) {
+ return this.generateTree(loc, CraftWorld.rand, type);
diff --git a/patches/server/0577-Get-entity-default-attributes.patch b/patches/server/0577-Get-entity-default-attributes.patch
new file mode 100644
index 0000000000..1f2a14bb52
--- /dev/null
+++ b/patches/server/0577-Get-entity-default-attributes.patch
@@ -0,0 +1,151 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Fri, 20 Aug 2021 13:03:21 -0700
+Subject: [PATCH] Get entity default attributes
+
+== AT ==
+public net.minecraft.world.entity.ai.attributes.AttributeSupplier getAttributeInstance(Lnet/minecraft/core/Holder;)Lnet/minecraft/world/entity/ai/attributes/AttributeInstance;
+
+diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..12135ffeacd648f6bc4d7d327059ea1a7e8c79c4
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeInstance.java
+@@ -0,0 +1,30 @@
++package io.papermc.paper.attribute;
++
++import net.minecraft.world.entity.ai.attributes.AttributeInstance;
++import org.bukkit.attribute.Attribute;
++import org.bukkit.attribute.AttributeModifier;
++import org.bukkit.craftbukkit.attribute.CraftAttributeInstance;
++
++import java.util.Collection;
++
++public class UnmodifiableAttributeInstance extends CraftAttributeInstance {
++
++ public UnmodifiableAttributeInstance(AttributeInstance handle, Attribute attribute) {
++ super(handle, attribute);
++ }
++
++ @Override
++ public void setBaseValue(double d) {
++ throw new UnsupportedOperationException("Cannot modify default attributes");
++ }
++
++ @Override
++ public void addModifier(AttributeModifier modifier) {
++ throw new UnsupportedOperationException("Cannot modify default attributes");
++ }
++
++ @Override
++ public void removeModifier(AttributeModifier modifier) {
++ throw new UnsupportedOperationException("Cannot modify default attributes");
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..ec9ebd2d539333293c51b7edfa18f18b066d7e43
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/attribute/UnmodifiableAttributeMap.java
+@@ -0,0 +1,32 @@
++package io.papermc.paper.attribute;
++
++import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
++import org.bukkit.attribute.Attributable;
++import org.bukkit.attribute.Attribute;
++import org.bukkit.attribute.AttributeInstance;
++import org.bukkit.craftbukkit.attribute.CraftAttribute;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++public class UnmodifiableAttributeMap implements Attributable {
++
++ private final AttributeSupplier handle;
++
++ public UnmodifiableAttributeMap(@NotNull AttributeSupplier handle) {
++ this.handle = handle;
++ }
++
++ @Override
++ public @Nullable AttributeInstance getAttribute(@NotNull Attribute attribute) {
++ net.minecraft.core.Holder<net.minecraft.world.entity.ai.attributes.Attribute> nmsAttribute = CraftAttribute.bukkitToMinecraftHolder(attribute);
++ if (!this.handle.hasAttribute(nmsAttribute)) {
++ return null;
++ }
++ return new UnmodifiableAttributeInstance(this.handle.getAttributeInstance(nmsAttribute), attribute);
++ }
++
++ @Override
++ public void registerAttribute(@NotNull Attribute attribute) {
++ throw new UnsupportedOperationException("Cannot register new attributes here");
++ }
++}
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+index e613ea0eba9a1d162b8f7dfe32c9c31d82f17dd2..6f780b76ebadb6155195b93f3e6d382141eb0bcc 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+@@ -560,6 +560,18 @@ public final class CraftMagicNumbers implements UnsafeValues {
+ }
+ return CraftItemStack.unwrap(itemToBeRepaired).isValidRepairItem(CraftItemStack.unwrap(repairMaterial));
+ }
++
++ @Override
++ public boolean hasDefaultEntityAttributes(NamespacedKey bukkitEntityKey) {
++ return net.minecraft.world.entity.ai.attributes.DefaultAttributes.hasSupplier(net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getValue(CraftNamespacedKey.toMinecraft(bukkitEntityKey)));
++ }
++
++ @Override
++ public org.bukkit.attribute.Attributable getDefaultEntityAttributes(NamespacedKey bukkitEntityKey) {
++ Preconditions.checkArgument(hasDefaultEntityAttributes(bukkitEntityKey), bukkitEntityKey + " doesn't have default attributes");
++ var supplier = net.minecraft.world.entity.ai.attributes.DefaultAttributes.getSupplier((net.minecraft.world.entity.EntityType<? extends net.minecraft.world.entity.LivingEntity>) net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getValue(CraftNamespacedKey.toMinecraft(bukkitEntityKey)));
++ return new io.papermc.paper.attribute.UnmodifiableAttributeMap(supplier);
++ }
+ // Paper end
+
+ /**
+diff --git a/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..fa9fb37993f4025f85dac084efb4b5eda0ede637
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/attribute/EntityTypeAttributesTest.java
+@@ -0,0 +1,40 @@
++package io.papermc.paper.attribute;
++
++import org.bukkit.attribute.Attributable;
++import org.bukkit.attribute.Attribute;
++import org.bukkit.attribute.AttributeInstance;
++import org.bukkit.attribute.AttributeModifier;
++import org.bukkit.entity.EntityType;
++import org.bukkit.support.environment.AllFeatures;
++import org.junit.jupiter.api.Test;
++
++import static org.junit.jupiter.api.Assertions.assertFalse;
++import static org.junit.jupiter.api.Assertions.assertNotNull;
++import static org.junit.jupiter.api.Assertions.assertThrows;
++import static org.junit.jupiter.api.Assertions.assertTrue;
++
++@AllFeatures
++public class EntityTypeAttributesTest {
++
++ @Test
++ public void testIllegalEntity() {
++ assertFalse(EntityType.EGG.hasDefaultAttributes());
++ assertThrows(IllegalArgumentException.class, EntityType.EGG::getDefaultAttributes);
++ }
++
++ @Test
++ public void testLegalEntity() {
++ assertTrue(EntityType.ZOMBIE.hasDefaultAttributes());
++ EntityType.ZOMBIE.getDefaultAttributes();
++ }
++
++ @Test
++ public void testUnmodifiabilityOfAttributable() {
++ Attributable attributable = EntityType.ZOMBIE.getDefaultAttributes();
++ assertThrows(UnsupportedOperationException.class, () -> attributable.registerAttribute(Attribute.ATTACK_DAMAGE));
++ AttributeInstance instance = attributable.getAttribute(Attribute.FOLLOW_RANGE);
++ assertNotNull(instance);
++ assertThrows(UnsupportedOperationException.class, () -> instance.addModifier(new AttributeModifier("test", 3, AttributeModifier.Operation.ADD_NUMBER)));
++ assertThrows(UnsupportedOperationException.class, () -> instance.setBaseValue(3.2));
++ }
++}
diff --git a/patches/server/0578-Left-handed-API.patch b/patches/server/0578-Left-handed-API.patch
new file mode 100644
index 0000000000..50bb0d4ebe
--- /dev/null
+++ b/patches/server/0578-Left-handed-API.patch
@@ -0,0 +1,27 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath <[email protected]>
+Date: Thu, 14 Oct 2021 12:36:58 -0500
+Subject: [PATCH] Left handed API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
+index 7cf42f62d91c131b1cab576979f85c58c3cecb3b..e226a99d00c990a4ca4f21b93fcae7a556e01dbb 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMob.java
+@@ -145,6 +145,16 @@ public abstract class CraftMob extends CraftLivingEntity implements Mob {
+ public int getMaxHeadPitch() {
+ return getHandle().getMaxHeadXRot();
+ }
++
++ @Override
++ public boolean isLeftHanded() {
++ return getHandle().isLeftHanded();
++ }
++
++ @Override
++ public void setLeftHanded(boolean leftHanded) {
++ getHandle().setLeftHanded(leftHanded);
++ }
+ // Paper end
+
+ // Paper start
diff --git a/patches/server/0579-Add-more-advancement-API.patch b/patches/server/0579-Add-more-advancement-API.patch
new file mode 100644
index 0000000000..70ac177106
--- /dev/null
+++ b/patches/server/0579-Add-more-advancement-API.patch
@@ -0,0 +1,213 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: syldium <[email protected]>
+Date: Fri, 9 Jul 2021 18:50:40 +0200
+Subject: [PATCH] Add more advancement API
+
+== AT ==
+public net.minecraft.advancements.Advancement decorateName(Lnet/minecraft/advancements/DisplayInfo;)Lnet/minecraft/network/chat/Component;
+
+Co-authored-by: Jake Potrebic <[email protected]>
+
+diff --git a/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java b/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..adac21ce6db3ff7a56dbcd6bffc02143c2db4046
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/advancement/PaperAdvancementDisplay.java
+@@ -0,0 +1,69 @@
++package io.papermc.paper.advancement;
++
++import io.papermc.paper.adventure.PaperAdventure;
++import net.kyori.adventure.text.Component;
++import net.minecraft.advancements.Advancement;
++import net.minecraft.advancements.AdvancementType;
++import net.minecraft.advancements.DisplayInfo;
++import org.bukkit.NamespacedKey;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.bukkit.inventory.ItemStack;
++import org.jetbrains.annotations.NotNull;
++import org.jetbrains.annotations.Nullable;
++
++public record PaperAdvancementDisplay(DisplayInfo handle) implements AdvancementDisplay {
++
++ @Override
++ public @NotNull Frame frame() {
++ return asPaperFrame(this.handle.getType());
++ }
++
++ @Override
++ public @NotNull Component title() {
++ return PaperAdventure.asAdventure(this.handle.getTitle());
++ }
++
++ @Override
++ public @NotNull Component description() {
++ return PaperAdventure.asAdventure(this.handle.getDescription());
++ }
++
++ @Override
++ public @NotNull ItemStack icon() {
++ return CraftItemStack.asBukkitCopy(this.handle.getIcon());
++ }
++
++ @Override
++ public boolean doesShowToast() {
++ return this.handle.shouldShowToast();
++ }
++
++ @Override
++ public boolean doesAnnounceToChat() {
++ return this.handle.shouldAnnounceChat();
++ }
++
++ @Override
++ public boolean isHidden() {
++ return this.handle.isHidden();
++ }
++
++ @Override
++ public @Nullable NamespacedKey backgroundPath() {
++ return this.handle.getBackground().map(CraftNamespacedKey::fromMinecraft).orElse(null);
++ }
++
++ @Override
++ public @NotNull Component displayName() {
++ return PaperAdventure.asAdventure(Advancement.decorateName(java.util.Objects.requireNonNull(this.handle, "cannot build display name for null handle, invalid state")));
++ }
++
++ public static @NotNull Frame asPaperFrame(final @NotNull AdvancementType frameType) {
++ return switch (frameType) {
++ case TASK -> Frame.TASK;
++ case CHALLENGE -> Frame.CHALLENGE;
++ case GOAL -> Frame.GOAL;
++ };
++ }
++}
+diff --git a/src/main/java/net/minecraft/advancements/DisplayInfo.java b/src/main/java/net/minecraft/advancements/DisplayInfo.java
+index 05de12414a3ad1c8f0f02f7973898dda84b89b82..6581cdbec730d5d184566e7b611369b3c424fecf 100644
+--- a/src/main/java/net/minecraft/advancements/DisplayInfo.java
++++ b/src/main/java/net/minecraft/advancements/DisplayInfo.java
+@@ -37,6 +37,7 @@ public class DisplayInfo {
+ private final boolean hidden;
+ private float x;
+ private float y;
++ public final io.papermc.paper.advancement.AdvancementDisplay paper = new io.papermc.paper.advancement.PaperAdvancementDisplay(this); // Paper - Add more advancement API
+
+ public DisplayInfo(
+ ItemStack icon,
+diff --git a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java
+index d119093d3a68c5a378fcd20860e36dbd2ba5024f..d12d9913f15c274df9c23f68e4e0304d41fccbdb 100644
+--- a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java
++++ b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancement.java
+@@ -35,12 +35,47 @@ public class CraftAdvancement implements org.bukkit.advancement.Advancement {
+ return new CraftAdvancementRequirements(this.handle.value().requirements());
+ }
+
++ // Paper start - Add more advancement API
+ @Override
+- public AdvancementDisplay getDisplay() {
+- if (this.handle.value().display().isEmpty()) {
+- return null;
++ public io.papermc.paper.advancement.AdvancementDisplay getDisplay() {
++ return this.handle.value().display().map(d -> d.paper).orElse(null);
++ }
++
++ @Deprecated
++ @io.papermc.paper.annotation.DoNotUse
++ public AdvancementDisplay getDisplay0() { // May be called by plugins via Commodore
++ return this.handle.value().display().map(CraftAdvancementDisplay::new).orElse(null);
++ }
++
++ @Override
++ public net.kyori.adventure.text.Component displayName() {
++ return io.papermc.paper.adventure.PaperAdventure.asAdventure(net.minecraft.advancements.Advancement.name(this.handle));
++ }
++
++ @Override
++ public org.bukkit.advancement.Advancement getParent() {
++ return this.handle.value().parent()
++ .map(net.minecraft.server.MinecraftServer.getServer().getAdvancements()::get)
++ .map(AdvancementHolder::toBukkit)
++ .orElse(null);
++ }
++
++ @Override
++ public Collection<org.bukkit.advancement.Advancement> getChildren() {
++ final com.google.common.collect.ImmutableList.Builder<org.bukkit.advancement.Advancement> children = com.google.common.collect.ImmutableList.<org.bukkit.advancement.Advancement>builder();
++ final net.minecraft.advancements.AdvancementNode advancementNode = net.minecraft.server.MinecraftServer.getServer().getAdvancements().tree().get(this.handle);
++ if (advancementNode != null) {
++ for (final net.minecraft.advancements.AdvancementNode child : advancementNode.children()) {
++ children.add(child.holder().toBukkit());
++ }
+ }
++ return children.build();
++ }
+
+- return new CraftAdvancementDisplay(this.handle.value().display().get());
++ @Override
++ public org.bukkit.advancement.Advancement getRoot() {
++ final net.minecraft.advancements.AdvancementNode advancementNode = net.minecraft.server.MinecraftServer.getServer().getAdvancements().tree().get(this.handle);
++ return java.util.Objects.requireNonNull(advancementNode, "could not find internal advancement node for advancement " + this.handle.id()).root().holder().toBukkit();
+ }
++ // Paper end - Add more advancement API
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementDisplay.java b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementDisplay.java
+index 8ca86852319d7463f60832bc98b825b0b4325995..62ada73302c6b3ce3fb2dcc8c31a1d9c0ac4fd09 100644
+--- a/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementDisplay.java
++++ b/src/main/java/org/bukkit/craftbukkit/advancement/CraftAdvancementDisplay.java
+@@ -6,6 +6,7 @@ import org.bukkit.craftbukkit.inventory.CraftItemStack;
+ import org.bukkit.craftbukkit.util.CraftChatMessage;
+ import org.bukkit.inventory.ItemStack;
+
++@Deprecated // Paper
+ public class CraftAdvancementDisplay implements org.bukkit.advancement.AdvancementDisplay {
+
+ private final DisplayInfo handle;
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
+index c9b789c2a904c2caff516ee9aeff4a6b368766f4..52a507ff2770bd46494e805956b4569f0d4d21ee 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
+@@ -458,6 +458,11 @@ public class Commodore {
+ super.visitMethodInsn(opcode, owner, name, "()Lcom/destroystokyo/paper/profile/PlayerProfile;", itf);
+ return;
+ }
++ if (owner.equals("org/bukkit/advancement/Advancement") && name.equals("getDisplay") && desc.endsWith(")Lorg/bukkit/advancement/AdvancementDisplay;")) {
++ super.visitTypeInsn(Opcodes.CHECKCAST, runtimeCbPkgPrefix() + "advancement/CraftAdvancement");
++ super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, runtimeCbPkgPrefix() + "advancement/CraftAdvancement", "getDisplay0", desc, false);
++ return;
++ }
+ // Paper end
+
+ if (modern) {
+diff --git a/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java b/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..6fe9c10ffd1dd5244ead05642609794623054cce
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/advancement/AdvancementFrameTest.java
+@@ -0,0 +1,26 @@
++package io.papermc.paper.advancement;
++
++import io.papermc.paper.adventure.PaperAdventure;
++import net.kyori.adventure.text.format.TextColor;
++import net.minecraft.advancements.AdvancementType;
++import net.minecraft.network.chat.contents.TranslatableContents;
++import org.bukkit.support.environment.Normal;
++import org.junit.jupiter.api.Test;
++
++import static org.junit.jupiter.api.Assertions.assertEquals;
++
++@Normal
++public class AdvancementFrameTest {
++
++ @Test
++ public void test() {
++ for (final AdvancementType advancementType : AdvancementType.values()) {
++ final TextColor expectedColor = PaperAdventure.asAdventure(advancementType.getChatColor());
++ final String expectedTranslationKey = ((TranslatableContents) advancementType.getDisplayName().getContents()).getKey();
++ final var frame = PaperAdvancementDisplay.asPaperFrame(advancementType);
++ assertEquals(expectedTranslationKey, frame.translationKey(), "The translation keys should be the same");
++ assertEquals(expectedColor, frame.color(), "The frame colors should be the same");
++ assertEquals(advancementType.getSerializedName(), AdvancementDisplay.Frame.NAMES.key(frame));
++ }
++ }
++}
diff --git a/patches/server/0580-Add-ItemFactory-getSpawnEgg-API.patch b/patches/server/0580-Add-ItemFactory-getSpawnEgg-API.patch
new file mode 100644
index 0000000000..b15779d35b
--- /dev/null
+++ b/patches/server/0580-Add-ItemFactory-getSpawnEgg-API.patch
@@ -0,0 +1,58 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: William Blake Galbreath <[email protected]>
+Date: Thu, 14 Oct 2021 12:09:39 -0500
+Subject: [PATCH] Add ItemFactory#getSpawnEgg API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
+index 68798c90f2a116d82f6f25e920c54c929df6fca9..a04840b77e2d84e754c9cfa79bc593608bc22c90 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
+@@ -9,6 +9,7 @@ import net.minecraft.core.HolderSet;
+ import net.minecraft.core.RegistryAccess;
+ import net.minecraft.core.component.DataComponentPatch;
+ import net.minecraft.core.registries.Registries;
++import net.minecraft.resources.ResourceLocation;
+ import net.minecraft.server.MinecraftServer;
+ import net.minecraft.tags.EnchantmentTags;
+ import net.minecraft.util.RandomSource;
+@@ -291,4 +292,19 @@ public final class CraftItemFactory implements ItemFactory {
+ new net.md_5.bungee.api.chat.TextComponent(customName));
+ }
+ // Paper end - bungee hover events
++
++ // Paper start - old getSpawnEgg API
++ // @Override // used to override, upstream added conflicting method, is called via Commodore now
++ @Deprecated
++ public ItemStack getSpawnEgg0(org.bukkit.entity.EntityType type) {
++ if (type == null) {
++ return null;
++ }
++ String typeId = type.getKey().toString();
++ net.minecraft.resources.ResourceLocation typeKey = ResourceLocation.parse(typeId);
++ net.minecraft.world.entity.EntityType<?> nmsType = net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getValue(typeKey);
++ net.minecraft.world.item.SpawnEggItem eggItem = net.minecraft.world.item.SpawnEggItem.byId(nmsType);
++ return eggItem == null ? null : new net.minecraft.world.item.ItemStack(eggItem).asBukkitMirror();
++ }
++ // Paper end - old getSpawnEgg API
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
+index 52a507ff2770bd46494e805956b4569f0d4d21ee..5a7aa491e0846ca8133bc4bb6e74b93cff85fb17 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
+@@ -465,6 +465,15 @@ public class Commodore {
+ }
+ // Paper end
+
++ // Paper start - ItemFactory#getSpawnEgg (paper had original method that returned ItemStack, upstream added identical but returned Material)
++ if (owner.equals("org/bukkit/inventory/ItemFactory") && name.equals("getSpawnEgg") && desc.equals("(Lorg/bukkit/entity/EntityType;)Lorg/bukkit/inventory/ItemStack;")) {
++ super.visitInsn(Opcodes.SWAP); // has 1 param, this moves the owner instance to the top for the checkcast
++ super.visitTypeInsn(Opcodes.CHECKCAST, runtimeCbPkgPrefix() + "inventory/CraftItemFactory");
++ super.visitInsn(Opcodes.SWAP); // moves param back to the the top of stack
++ super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, runtimeCbPkgPrefix() + "inventory/CraftItemFactory", "getSpawnEgg0", desc, false);
++ return;
++ }
++ // Paper end - ItemFactory#getSpawnEgg
+ if (modern) {
+ if (owner.equals("org/bukkit/Material") || (instantiatedMethodType != null && instantiatedMethodType.getDescriptor().startsWith("(Lorg/bukkit/Material;)"))) {
+ switch (name) {
diff --git a/patches/server/0581-Add-critical-damage-API.patch b/patches/server/0581-Add-critical-damage-API.patch
new file mode 100644
index 0000000000..a4fa089b63
--- /dev/null
+++ b/patches/server/0581-Add-critical-damage-API.patch
@@ -0,0 +1,101 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: dodison <[email protected]>
+Date: Mon, 26 Jul 2021 17:32:36 +0200
+Subject: [PATCH] Add critical damage API
+
+
+diff --git a/src/main/java/net/minecraft/world/damagesource/DamageSource.java b/src/main/java/net/minecraft/world/damagesource/DamageSource.java
+index c1d121d83591ca1b5bf9d9406c9622b4f24eafef..aee26dd78953ff43306aaa64161f5b9edcdd4b83 100644
+--- a/src/main/java/net/minecraft/world/damagesource/DamageSource.java
++++ b/src/main/java/net/minecraft/world/damagesource/DamageSource.java
+@@ -276,4 +276,18 @@ public class DamageSource {
+ public Holder<DamageType> typeHolder() {
+ return this.type;
+ }
++
++ // Paper start - add critical damage API
++ private boolean critical;
++ public boolean isCritical() {
++ return this.critical;
++ }
++ public DamageSource critical() {
++ return this.critical(true);
++ }
++ public DamageSource critical(boolean critical) {
++ this.critical = critical;
++ return this;
++ }
++ // Paper end - add critical damage API
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
+index 13f21822d0eb1d167b9b71addb46c4f508301c8d..4e33c6c612939e6970d5984825115768ebb6a5ea 100644
+--- a/src/main/java/net/minecraft/world/entity/player/Player.java
++++ b/src/main/java/net/minecraft/world/entity/player/Player.java
+@@ -1261,6 +1261,7 @@ public abstract class Player extends LivingEntity {
+
+ flag2 = flag2 && !this.level().paperConfig().entities.behavior.disablePlayerCrits; // Paper - Toggleable player crits
+ if (flag2) {
++ damagesource = damagesource.critical(true); // Paper start - critical damage API
+ f *= 1.5F;
+ }
+
+@@ -1320,7 +1321,7 @@ public abstract class Player extends LivingEntity {
+ float f7 = this.getEnchantedDamage(entityliving2, f6, damagesource) * f2;
+
+ // CraftBukkit start - Only apply knockback if the damage hits
+- if (!entityliving2.hurtServer((ServerLevel) this.level(), this.damageSources().playerAttack(this).sweep(), f7)) {
++ if (!entityliving2.hurtServer((ServerLevel) this.level(), this.damageSources().playerAttack(this).sweep().critical(flag2), f7)) { // Paper - add critical damage API
+ continue;
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java
+index 52765e1b9f5a026e7108ff5a7d97681cdb2870e9..aab7d546317d93876ccd0e02c0631ccc7c8f9bf5 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java
+@@ -465,6 +465,7 @@ public abstract class AbstractArrow extends Projectile {
+ entityliving.setLastHurtMob(entity);
+ }
+
++ if (this.isCritArrow()) damagesource = damagesource.critical(); // Paper - add critical damage API
+ boolean flag = entity.getType() == EntityType.ENDERMAN;
+ int k = entity.getRemainingFireTicks();
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+index 24f86724012bb8bcd6d24683a1c78ce66b42ca30..a5285a8952b2d99bfbb928b1bb31d3ddcf8ed57b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+@@ -1078,7 +1078,7 @@ public class CraftEventFactory {
+ return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.BLOCK_EXPLOSION, bukkitDamageSource, modifiers, modifierFunctions, cancelled);
+ }
+ DamageCause damageCause = (damager.getBukkitEntity() instanceof org.bukkit.entity.TNTPrimed) ? DamageCause.BLOCK_EXPLOSION : DamageCause.ENTITY_EXPLOSION;
+- return CraftEventFactory.callEntityDamageEvent(damager, entity, damageCause, bukkitDamageSource, modifiers, modifierFunctions, cancelled);
++ return CraftEventFactory.callEntityDamageEvent(damager, entity, damageCause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API
+ } else if (damager != null || source.getDirectEntity() != null) {
+ DamageCause cause = (source.isSweep()) ? DamageCause.ENTITY_SWEEP_ATTACK : DamageCause.ENTITY_ATTACK;
+
+@@ -1104,7 +1104,7 @@ public class CraftEventFactory {
+ cause = DamageCause.MAGIC;
+ }
+
+- return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled);
++ return CraftEventFactory.callEntityDamageEvent(damager, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API
+ } else if (source.is(DamageTypes.FELL_OUT_OF_WORLD)) {
+ return CraftEventFactory.callEntityDamageEvent(source.getDirectBlock(), source.getDirectBlockState(), entity, DamageCause.VOID, bukkitDamageSource, modifiers, modifierFunctions, cancelled);
+ } else if (source.is(DamageTypes.LAVA)) {
+@@ -1164,13 +1164,13 @@ public class CraftEventFactory {
+ cause = DamageCause.CUSTOM;
+ }
+
+- return CraftEventFactory.callEntityDamageEvent((Entity) null, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled);
++ return CraftEventFactory.callEntityDamageEvent((Entity) null, entity, cause, bukkitDamageSource, modifiers, modifierFunctions, cancelled, source.isCritical()); // Paper - add critical damage API
+ }
+
+- private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, org.bukkit.damage.DamageSource bukkitDamageSource, Map<DamageModifier, Double> modifiers, Map<DamageModifier, Function<? super Double, Double>> modifierFunctions, boolean cancelled) {
++ private static EntityDamageEvent callEntityDamageEvent(Entity damager, Entity damagee, DamageCause cause, org.bukkit.damage.DamageSource bukkitDamageSource, Map<DamageModifier, Double> modifiers, Map<DamageModifier, Function<? super Double, Double>> modifierFunctions, boolean cancelled, boolean critical) { // Paper - add critical damage API
+ EntityDamageEvent event;
+ if (damager != null) {
+- event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions);
++ event = new EntityDamageByEntityEvent(damager.getBukkitEntity(), damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions, critical);
+ } else {
+ event = new EntityDamageEvent(damagee.getBukkitEntity(), cause, bukkitDamageSource, modifiers, modifierFunctions);
+ }
diff --git a/patches/server/0582-Fix-issues-with-mob-conversion.patch b/patches/server/0582-Fix-issues-with-mob-conversion.patch
new file mode 100644
index 0000000000..c286b73b30
--- /dev/null
+++ b/patches/server/0582-Fix-issues-with-mob-conversion.patch
@@ -0,0 +1,74 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 24 Oct 2021 20:29:45 -0700
+Subject: [PATCH] Fix issues with mob conversion
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
+index 6f6454bcec7e0d1cefbf818fc2fc6eb90adeec83..3972e2ed0554e2550519e994888e068df0a151e5 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java
+@@ -94,12 +94,19 @@ public class Skeleton extends AbstractSkeleton {
+ }
+
+ protected void doFreezeConversion() {
+- this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), (entityskeletonstray) -> {
++ final Stray stray = this.convertTo(EntityType.STRAY, ConversionParams.single(this, true, true), (entityskeletonstray) -> { // Paper - Fix issues with mob conversion; reset conversion time to prevent event spam
+ if (!this.isSilent()) {
+ this.level().levelEvent((Player) null, 1048, this.blockPosition(), 0);
+ }
+
+- }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN); // CraftBukkit - add spawn and transform reasons
++ }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.FROZEN, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.FROZEN);// CraftBukkit - add spawn and transform reasons
++
++ // Paper start - Fix issues with mob conversion; reset conversion time to prevent event spam
++ if (stray == null) {
++ this.conversionTime = 300;
++ }
++ // Paper end - Fix issues with mob conversion
++
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
+index 34fff71edbdcefe87985f31cbf1ef282435ace06..92270912ef26924f611a1df7cb3d5b485b0a262d 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
++++ b/src/main/java/net/minecraft/world/entity/monster/hoglin/Hoglin.java
+@@ -241,9 +241,15 @@ public class Hoglin extends Animal implements Enemy, HoglinBase {
+ }
+
+ private void finishConversion() {
+- this.convertTo(EntityType.ZOGLIN, ConversionParams.single(this, true, false), (entityzoglin) -> {
++ net.minecraft.world.entity.Entity converted = this.convertTo(EntityType.ZOGLIN, ConversionParams.single(this, true, false), (entityzoglin) -> {
+ entityzoglin.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));
+ }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.PIGLIN_ZOMBIFIED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PIGLIN_ZOMBIFIED); // CraftBukkit - add spawn and transform reasons
++
++ // Paper start - Fix issues with mob conversion; reset to prevent event spam
++ if (converted == null) {
++ this.timeInOverworld = 0;
++ }
++ // Paper end - Fix issues with mob conversion
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java b/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java
+index e2075fb22596b6dc4dbbb9c20c91c63f0ddb7ba7..e3a8aa0ae0d1fa6f2b697e20464224f8b8c6b8ea 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java
++++ b/src/main/java/net/minecraft/world/entity/monster/piglin/AbstractPiglin.java
+@@ -100,9 +100,15 @@ public abstract class AbstractPiglin extends Monster {
+ }
+
+ protected void finishConversion(ServerLevel world) {
+- this.convertTo(EntityType.ZOMBIFIED_PIGLIN, ConversionParams.single(this, true, true), (entitypigzombie) -> {
++ net.minecraft.world.entity.Entity converted = this.convertTo(EntityType.ZOMBIFIED_PIGLIN, ConversionParams.single(this, true, true), (entitypigzombie) -> { // Paper - Fix issues with mob conversion; reset to prevent event spam
+ entitypigzombie.addEffect(new MobEffectInstance(MobEffects.CONFUSION, 200, 0));
+ }, org.bukkit.event.entity.EntityTransformEvent.TransformReason.PIGLIN_ZOMBIFIED, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.PIGLIN_ZOMBIFIED); // CraftBukkit - add spawn and transform reasons
++
++ // Paper start - Fix issues with mob conversion; reset to prevent event spam
++ if (converted == null) {
++ this.timeInOverworld = 0;
++ }
++ // Paper end - Fix issues with mob conversion
+ }
+
+ public boolean isAdult() {
diff --git a/patches/server/0583-Add-hasCollision-methods-to-various-places.patch b/patches/server/0583-Add-hasCollision-methods-to-various-places.patch
new file mode 100644
index 0000000000..421b0ba92e
--- /dev/null
+++ b/patches/server/0583-Add-hasCollision-methods-to-various-places.patch
@@ -0,0 +1,56 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 4 Nov 2021 11:50:40 -0700
+Subject: [PATCH] Add hasCollision methods to various places
+
+== AT ==
+public net.minecraft.world.level.block.state.BlockBehaviour hasCollision
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
+index 9c8aac69f01db647e20d49d272ccc107a7edceaf..d5b495b5a3ca7f4411d1a700f7149042a16509f1 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
+@@ -457,6 +457,11 @@ public class CraftBlock implements Block {
+ public boolean isSolid() {
+ return this.getNMS().blocksMotion();
+ }
++
++ @Override
++ public boolean isCollidable() {
++ return getNMS().getBlock().hasCollision;
++ }
+ // Paper end
+
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
+index 1002123cd0c6f57cecc4e80f5f21cc6ff5886d37..e96023b71845526383288917e8d7c5759a4c0e9b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockState.java
+@@ -341,4 +341,11 @@ public class CraftBlockState implements BlockState {
+ public BlockState copy(Location location) {
+ return new CraftBlockState(this, location);
+ }
++
++ // Paper start
++ @Override
++ public boolean isCollidable() {
++ return this.data.getBlock().hasCollision;
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java
+index 2d8a509446c0ed0d7358f10f67ef29c4df683696..785d3fe4929e008d4150f3ecab258fd5b6d7cdaf 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockType.java
+@@ -241,4 +241,11 @@ public class CraftBlockType<B extends BlockData> implements BlockType.Typed<B>,
+ return this.block.getDescriptionId();
+ }
+ // Paper end - add Translatable
++
++ // Paper start - hasCollision API
++ @Override
++ public boolean hasCollision() {
++ return this.block.hasCollision;
++ }
++ // Paper end - hasCollision API
+ }
diff --git a/patches/server/0584-Goat-ram-API.patch b/patches/server/0584-Goat-ram-API.patch
new file mode 100644
index 0000000000..21d6196f4c
--- /dev/null
+++ b/patches/server/0584-Goat-ram-API.patch
@@ -0,0 +1,42 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Seggan <[email protected]>
+Date: Thu, 5 Aug 2021 13:10:27 -0400
+Subject: [PATCH] Goat ram API
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
+index cccf0084d273eaded91abe249d39a843f11d351b..14e02f9b0169db8388c515a68315ad5cc3f13d22 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
++++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java
+@@ -395,4 +395,15 @@ public class Goat extends Animal {
+ public static boolean checkGoatSpawnRules(EntityType<? extends Animal> entityType, LevelAccessor world, EntitySpawnReason spawnReason, BlockPos pos, RandomSource random) {
+ return world.getBlockState(pos.below()).is(BlockTags.GOATS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos);
+ }
++
++ // Paper start - Goat ram API
++ public void ram(net.minecraft.world.entity.LivingEntity entity) {
++ Brain<Goat> brain = this.getBrain();
++ brain.setMemory(MemoryModuleType.RAM_TARGET, entity.position());
++ brain.eraseMemory(MemoryModuleType.RAM_COOLDOWN_TICKS);
++ brain.eraseMemory(MemoryModuleType.BREED_TARGET);
++ brain.eraseMemory(MemoryModuleType.TEMPTING_PLAYER);
++ brain.setActiveActivityIfPossible(net.minecraft.world.entity.schedule.Activity.RAM);
++ }
++ // Paper end - Goat ram API
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java
+index 65fcb36e849da6949c123a6f3672b485036f368e..2c21de478bff9cdf13ba46cd041831d54c11e924 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftGoat.java
+@@ -48,4 +48,11 @@ public class CraftGoat extends CraftAnimals implements Goat {
+ public void setScreaming(boolean screaming) {
+ this.getHandle().setScreamingGoat(screaming);
+ }
++
++ // Paper start - Goat ram API
++ @Override
++ public void ram(@org.jetbrains.annotations.NotNull org.bukkit.entity.LivingEntity entity) {
++ this.getHandle().ram(((CraftLivingEntity) entity).getHandle());
++ }
++ // Paper end
+ }
diff --git a/patches/server/0585-Add-API-for-resetting-a-single-score.patch b/patches/server/0585-Add-API-for-resetting-a-single-score.patch
new file mode 100644
index 0000000000..b9dd1c2be3
--- /dev/null
+++ b/patches/server/0585-Add-API-for-resetting-a-single-score.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: booky10 <[email protected]>
+Date: Fri, 5 Nov 2021 21:01:36 +0100
+Subject: [PATCH] Add API for resetting a single score
+
+It was only possible to reset all scores for a specific entry, instead of resetting only specific scores.
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java
+index 29e24461e29e4cf3d31497198debcde18761ad73..ceb1a39c02c3cfa7632a0fdca414c7046888fcb1 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java
++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScore.java
+@@ -66,4 +66,12 @@ final class CraftScore implements Score {
+ public CraftScoreboard getScoreboard() {
+ return this.objective.getScoreboard();
+ }
++
++ // Paper start
++ @Override
++ public void resetScore() {
++ Scoreboard board = this.objective.checkState().board;
++ board.resetSinglePlayerScore(entry, this.objective.getHandle());
++ }
++ // Paper end
+ }
diff --git a/patches/server/0586-Add-Raw-Byte-Entity-Serialization.patch b/patches/server/0586-Add-Raw-Byte-Entity-Serialization.patch
new file mode 100644
index 0000000000..e2701e2fc7
--- /dev/null
+++ b/patches/server/0586-Add-Raw-Byte-Entity-Serialization.patch
@@ -0,0 +1,90 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Mariell Hoversholm <[email protected]>
+Date: Sun, 24 Oct 2021 16:20:31 -0400
+Subject: [PATCH] Add Raw Byte Entity Serialization
+
+== AT ==
+public net.minecraft.world.entity.Entity setLevel(Lnet/minecraft/world/level/Level;)V
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index f9e52b8794d62cc79c420008a1a95707efa14321..ee2625301ebb1dd601618ae43915891f3b57b7c3 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -2284,6 +2284,15 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ }
+ }
+
++ // Paper start - Entity serialization api
++ public boolean serializeEntity(CompoundTag compound) {
++ List<Entity> pass = new java.util.ArrayList<>(this.getPassengers());
++ this.passengers = ImmutableList.of();
++ boolean result = save(compound);
++ this.passengers = ImmutableList.copyOf(pass);
++ return result;
++ }
++ // Paper end - Entity serialization api
+ public boolean save(CompoundTag nbt) {
+ return this.isPassenger() ? false : this.saveAsPassenger(nbt);
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+index 888a75423ac90ca85308eeb6d67bac5348bf31e0..b13c947d2cfe085017b30cb0f8340dd64f338914 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+@@ -1083,6 +1083,18 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
+ }
+ // Paper end - tracked players API
+
++ // Paper start - raw entity serialization API
++ @Override
++ public boolean spawnAt(Location location, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason reason) {
++ Preconditions.checkNotNull(location, "location cannot be null");
++ Preconditions.checkNotNull(reason, "reason cannot be null");
++ this.entity.setLevel(((CraftWorld) location.getWorld()).getHandle());
++ this.entity.setPos(location.getX(), location.getY(), location.getZ());
++ this.entity.setRot(location.getYaw(), location.getPitch());
++ return !this.entity.valid && this.entity.level().addFreshEntity(this.entity, reason);
++ }
++ // Paper end - raw entity serialization API
++
+ // Paper start - missing entity api
+ @Override
+ public boolean isInvisible() { // Paper - moved up from LivingEntity
+diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+index 6f780b76ebadb6155195b93f3e6d382141eb0bcc..d7698f8ae59195458148397406a104224676b76e 100644
+--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+@@ -510,7 +510,33 @@ public final class CraftMagicNumbers implements UnsafeValues {
+ return CraftItemStack.asCraftMirror(net.minecraft.world.item.ItemStack.parse(MinecraftServer.getServer().registryAccess(), compound).orElseThrow());
+ }
+
+- private byte[] serializeNbtToBytes(CompoundTag compound) {
++ @Override
++ public byte[] serializeEntity(org.bukkit.entity.Entity entity) {
++ Preconditions.checkNotNull(entity, "null cannot be serialized");
++ Preconditions.checkArgument(entity instanceof org.bukkit.craftbukkit.entity.CraftEntity, "only CraftEntities can be serialized");
++
++ net.minecraft.nbt.CompoundTag compound = new net.minecraft.nbt.CompoundTag();
++ ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().serializeEntity(compound);
++ return serializeNbtToBytes(compound);
++ }
++
++ @Override
++ public org.bukkit.entity.Entity deserializeEntity(byte[] data, org.bukkit.World world, boolean preserveUUID) {
++ Preconditions.checkNotNull(data, "null cannot be deserialized");
++ Preconditions.checkArgument(data.length > 0, "cannot deserialize nothing");
++
++ net.minecraft.nbt.CompoundTag compound = deserializeNbtFromBytes(data);
++ int dataVersion = compound.getInt("DataVersion");
++ compound = (net.minecraft.nbt.CompoundTag) MinecraftServer.getServer().fixerUpper.update(References.ENTITY, new Dynamic<>(NbtOps.INSTANCE, compound), dataVersion, this.getDataVersion()).getValue();
++ if (!preserveUUID) {
++ // Generate a new UUID so we don't have to worry about deserializing the same entity twice
++ compound.remove("UUID");
++ }
++ return net.minecraft.world.entity.EntityType.create(compound, ((org.bukkit.craftbukkit.CraftWorld) world).getHandle(), net.minecraft.world.entity.EntitySpawnReason.LOAD)
++ .orElseThrow(() -> new IllegalArgumentException("An ID was not found for the data. Did you downgrade?")).getBukkitEntity();
++ }
++
++ private byte[] serializeNbtToBytes(net.minecraft.nbt.CompoundTag compound) {
+ compound.putInt("DataVersion", getDataVersion());
+ java.io.ByteArrayOutputStream outputStream = new java.io.ByteArrayOutputStream();
+ try {
diff --git a/patches/server/0587-Vanilla-command-permission-fixes.patch b/patches/server/0587-Vanilla-command-permission-fixes.patch
new file mode 100644
index 0000000000..90d31ae40c
--- /dev/null
+++ b/patches/server/0587-Vanilla-command-permission-fixes.patch
@@ -0,0 +1,79 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Wed, 25 Aug 2021 13:19:53 -0700
+Subject: [PATCH] Vanilla command permission fixes
+
+Fixes permission checks for vanilla commands which don't have a
+requirement, as well as for namespaced vanilla commands.
+
+== AT ==
+public-f com.mojang.brigadier.tree.CommandNode requirement
+
+diff --git a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java
+index 899008b2980d13f1be6280cd8cb959c53a29bebf..d5f7da3502575f6847f3c22ab0e942848a7c6031 100644
+--- a/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java
++++ b/src/main/java/com/mojang/brigadier/builder/ArgumentBuilder.java
+@@ -14,9 +14,17 @@ import java.util.Collections;
+ import java.util.function.Predicate;
+
+ public abstract class ArgumentBuilder<S, T extends ArgumentBuilder<S, T>> {
++ // Paper start - Vanilla command permission fixes
++ private static final Predicate<Object> DEFAULT_REQUIREMENT = s -> true;
++
++ @SuppressWarnings("unchecked")
++ public static <S> Predicate<S> defaultRequirement() {
++ return (Predicate<S>) DEFAULT_REQUIREMENT;
++ }
++ // Paper end - Vanilla command permission fixes
+ private final RootCommandNode<S> arguments = new RootCommandNode<>();
+ private Command<S> command;
+- private Predicate<S> requirement = s -> true;
++ private Predicate<S> requirement = defaultRequirement(); // Paper - Vanilla command permission fixes
+ private CommandNode<S> target;
+ private RedirectModifier<S> modifier = null;
+ private boolean forks;
+diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java
+index d602c713696e23ba6a2d542b2e9e2cce46d79a66..800d1756db8c27b7d129a90addc125c4fc81e134 100644
+--- a/src/main/java/net/minecraft/commands/Commands.java
++++ b/src/main/java/net/minecraft/commands/Commands.java
+@@ -261,6 +261,13 @@ public class Commands {
+ PublishCommand.register(this.dispatcher);
+ }
+
++ // Paper start - Vanilla command permission fixes
++ for (final CommandNode<CommandSourceStack> node : this.dispatcher.getRoot().getChildren()) {
++ if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.<CommandSourceStack>defaultRequirement()) {
++ node.requirement = stack -> stack.source == CommandSource.NULL || stack.getBukkitSender().hasPermission(org.bukkit.craftbukkit.command.VanillaCommandWrapper.getPermission(node));
++ }
++ }
++ // Paper end - Vanilla command permission fixes
+ // CraftBukkit start
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+index 9b453830e4b949d67c2a01adc309ddc6ad019be1..35b05f9321ddbbbdf62f4bf726b58cf1205f0cfb 100644
+--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+@@ -91,7 +91,21 @@ public final class VanillaCommandWrapper extends BukkitCommand {
+ }
+
+ public static String getPermission(CommandNode<CommandSourceStack> vanillaCommand) {
+- return "minecraft.command." + ((vanillaCommand.getRedirect() == null) ? vanillaCommand.getName() : vanillaCommand.getRedirect().getName());
++ // Paper start - Vanilla command permission fixes
++ while (vanillaCommand.getRedirect() != null) {
++ vanillaCommand = vanillaCommand.getRedirect();
++ }
++ final String commandName = vanillaCommand.getName();
++ return "minecraft.command." + stripDefaultNamespace(commandName);
++ }
++
++ private static String stripDefaultNamespace(final String maybeNamespaced) {
++ final String prefix = "minecraft:";
++ if (maybeNamespaced.startsWith(prefix)) {
++ return maybeNamespaced.substring(prefix.length());
++ }
++ return maybeNamespaced;
++ // Paper end - Vanilla command permission fixes
+ }
+
+ private String toDispatcher(String[] args, String name) {
diff --git a/patches/server/0588-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch b/patches/server/0588-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch
new file mode 100644
index 0000000000..f403613911
--- /dev/null
+++ b/patches/server/0588-Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch
@@ -0,0 +1,68 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Thu, 11 Mar 2021 03:03:32 -0800
+Subject: [PATCH] Do not run close logic for inventories on chunk unload
+
+Still call the event and change the active container though. We
+want to avoid close logic because it's possible to load the
+chunk through it. This should also be OK from a leak prevention/
+state desync POV because the TE is getting unloaded anyways.
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index e6ff03143b3639f375323393814b1256b98687ad..2ac09e8eece6ff772c94bb1efd01cc1c45194435 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -1245,9 +1245,13 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ // Spigot Start
+ for (net.minecraft.world.level.block.entity.BlockEntity tileentity : chunk.getBlockEntities().values()) {
+ if (tileentity instanceof net.minecraft.world.Container) {
++ // Paper start - this area looks like it can load chunks, change the behavior
++ // chests for example can apply physics to the world
++ // so instead we just change the active container and call the event
+ for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) {
+- h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason
++ ((org.bukkit.craftbukkit.entity.CraftHumanEntity) h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason
+ }
++ // Paper end - this area looks like it can load chunks, change the behavior
+ }
+ }
+ // Spigot End
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index f2cc608d2cf040be2912b604f0d6cab21e33ded0..215be207e29254312bb85f259a684a7de6876aa9 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -1966,6 +1966,18 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId));
+ this.doCloseContainer();
+ }
++ // Paper start - special close for unloaded inventory
++ @Override
++ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
++ // copied from above
++ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit
++ // Paper end
++ // copied from below
++ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId));
++ this.containerMenu = this.inventoryMenu;
++ // do not run close logic
++ }
++ // Paper end - special close for unloaded inventory
+
+ @Override
+ public void doCloseContainer() {
+diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java
+index 4e33c6c612939e6970d5984825115768ebb6a5ea..81e3072ac80623db47ddc0b9c52e0e56dab9e10e 100644
+--- a/src/main/java/net/minecraft/world/entity/player/Player.java
++++ b/src/main/java/net/minecraft/world/entity/player/Player.java
+@@ -547,6 +547,11 @@ public abstract class Player extends LivingEntity {
+ this.containerMenu = this.inventoryMenu;
+ }
+ // Paper end - Inventory close reason
++ // Paper start - special close for unloaded inventory
++ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) {
++ this.containerMenu = this.inventoryMenu;
++ }
++ // Paper end - special close for unloaded inventory
+
+ public void closeContainer() {
+ this.containerMenu = this.inventoryMenu;
diff --git a/patches/server/0589-Fix-GameProfileCache-concurrency.patch b/patches/server/0589-Fix-GameProfileCache-concurrency.patch
new file mode 100644
index 0000000000..6cb7e5235f
--- /dev/null
+++ b/patches/server/0589-Fix-GameProfileCache-concurrency.patch
@@ -0,0 +1,126 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Sat, 11 Jul 2020 05:09:28 -0700
+Subject: [PATCH] Fix GameProfileCache concurrency
+
+Separate lookup and state access locks prevent lookups
+from stalling simple state access/write calls
+
+diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java
+index 774d81c702edb76a2f6184d4dc53687de6734a79..34b4166adfae8ff7d1eb73d56a72931b005330a7 100644
+--- a/src/main/java/net/minecraft/server/players/GameProfileCache.java
++++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java
+@@ -60,6 +60,11 @@ public class GameProfileCache {
+ @Nullable
+ private Executor executor;
+
++ // Paper start - Fix GameProfileCache concurrency
++ protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock();
++ protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock();
++ // Paper end - Fix GameProfileCache concurrency
++
+ public GameProfileCache(GameProfileRepository profileRepository, File cacheFile) {
+ this.profileRepository = profileRepository;
+ this.file = cacheFile;
+@@ -67,11 +72,13 @@ public class GameProfileCache {
+ }
+
+ private void safeAdd(GameProfileCache.GameProfileInfo entry) {
++ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
+ GameProfile gameprofile = entry.getProfile();
+
+ entry.setLastAccess(this.getNextOperation());
+ this.profilesByName.put(gameprofile.getName().toLowerCase(Locale.ROOT), entry);
+ this.profilesByUUID.put(gameprofile.getId(), entry);
++ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
+ }
+
+ private static Optional<GameProfile> lookupGameProfile(GameProfileRepository repository, String name) {
+@@ -128,17 +135,20 @@ public class GameProfileCache {
+
+ // Paper start
+ public @Nullable GameProfile getProfileIfCached(String name) {
++ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
+ GameProfileCache.GameProfileInfo entry = this.profilesByName.get(name.toLowerCase(Locale.ROOT));
+ if (entry == null) {
+ return null;
+ }
+ entry.setLastAccess(this.getNextOperation());
+ return entry.getProfile();
++ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
+ }
+ // Paper end
+
+ public Optional<GameProfile> get(String name) {
+ String s1 = name.toLowerCase(Locale.ROOT);
++ boolean stateLocked = true; try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
+ GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1);
+ boolean flag = false;
+
+@@ -154,8 +164,12 @@ public class GameProfileCache {
+ if (usercache_usercacheentry != null) {
+ usercache_usercacheentry.setLastAccess(this.getNextOperation());
+ optional = Optional.of(usercache_usercacheentry.getProfile());
++ stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency
+ } else {
++ stateLocked = false; this.stateLock.unlock(); // Paper - Fix GameProfileCache concurrency
++ try { this.lookupLock.lock(); // Paper - Fix GameProfileCache concurrency
+ optional = GameProfileCache.lookupGameProfile(this.profileRepository, name); // CraftBukkit - use correct case for offline players
++ } finally { this.lookupLock.unlock(); } // Paper - Fix GameProfileCache concurrency
+ if (optional.isPresent()) {
+ this.add((GameProfile) optional.get());
+ flag = false;
+@@ -167,6 +181,7 @@ public class GameProfileCache {
+ }
+
+ return optional;
++ } finally { if (stateLocked) { this.stateLock.unlock(); } } // Paper - Fix GameProfileCache concurrency
+ }
+
+ public CompletableFuture<Optional<GameProfile>> getAsync(String username) {
+@@ -191,6 +206,7 @@ public class GameProfileCache {
+ }
+
+ public Optional<GameProfile> get(UUID uuid) {
++ try { this.stateLock.lock(); // Paper - Fix GameProfileCache concurrency
+ GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByUUID.get(uuid);
+
+ if (usercache_usercacheentry == null) {
+@@ -199,6 +215,7 @@ public class GameProfileCache {
+ usercache_usercacheentry.setLastAccess(this.getNextOperation());
+ return Optional.of(usercache_usercacheentry.getProfile());
+ }
++ } finally { this.stateLock.unlock(); } // Paper - Fix GameProfileCache concurrency
+ }
+
+ public void setExecutor(Executor executor) {
+@@ -279,7 +296,7 @@ public class GameProfileCache {
+ JsonArray jsonarray = new JsonArray();
+ DateFormat dateformat = GameProfileCache.createDateFormat();
+
+- this.getTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((usercache_usercacheentry) -> { // Spigot
++ this.listTopMRUProfiles(org.spigotmc.SpigotConfig.userCacheCap).forEach((usercache_usercacheentry) -> { // Spigot // Paper - Fix GameProfileCache concurrency
+ jsonarray.add(GameProfileCache.writeGameProfile(usercache_usercacheentry, dateformat));
+ });
+ String s = this.gson.toJson(jsonarray);
+@@ -320,8 +337,19 @@ public class GameProfileCache {
+ }
+
+ private Stream<GameProfileCache.GameProfileInfo> getTopMRUProfiles(int limit) {
+- return ImmutableList.copyOf(this.profilesByUUID.values()).stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit((long) limit);
++ // Paper start - Fix GameProfileCache concurrency
++ return this.listTopMRUProfiles(limit).stream();
++ }
++
++ private List<GameProfileCache.GameProfileInfo> listTopMRUProfiles(int limit) {
++ try {
++ this.stateLock.lock();
++ return this.profilesByUUID.values().stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit(limit).toList();
++ } finally {
++ this.stateLock.unlock();
++ }
+ }
++ // Paper end - Fix GameProfileCache concurrency
+
+ private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo entry, DateFormat dateFormat) {
+ JsonObject jsonobject = new JsonObject();
diff --git a/patches/server/0590-Improve-and-expand-AsyncCatcher.patch b/patches/server/0590-Improve-and-expand-AsyncCatcher.patch
new file mode 100644
index 0000000000..ae6607cb0d
--- /dev/null
+++ b/patches/server/0590-Improve-and-expand-AsyncCatcher.patch
@@ -0,0 +1,227 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Wed, 25 Aug 2021 20:17:12 -0700
+Subject: [PATCH] Improve and expand AsyncCatcher
+
+Log when the async catcher is tripped
+ The chunk system can swallow the exception given it's all
+ built with completablefuture, so ensure it is at least printed.
+
+Add/move several async catchers
+
+Async catch modifications to critical entity state
+ These used to be here from Spigot, but were dropped with 1.17.
+ Now in 1.17, this state is _even more_ critical than it was before,
+ so these must exist to catch stupid plugins.
+
+Co-authored-by: Jake Potrebic <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index 2df548b68e56a309af4b89f8f1174dde3b046e7d..dcc87047769094241a0f0b682dc31cc4767ab5e9 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -1651,6 +1651,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ }
+
+ public void internalTeleport(PositionMoveRotation positionmoverotation, Set<Relative> set) {
++ org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper
+ // Paper start - Prevent teleporting dead entities
+ if (player.isRemoved()) {
+ LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName());
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index b7c854614643cc6fcb78ce7ac378f5dbbb8eb305..fb3cec13dd9e426904ce28a8755e6cc57296a42e 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -1143,7 +1143,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ }
+
+ public boolean addEffect(MobEffectInstance mobeffect, @Nullable Entity entity, EntityPotionEffectEvent.Cause cause) {
+- org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot
++ // org.spigotmc.AsyncCatcher.catchOp("effect add"); // Spigot // Paper - move to API
+ if (this.isTickingEffects) {
+ this.effectsToProcess.add(new ProcessableEffect(mobeffect, cause));
+ return true;
+diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+index 4eb0b0969325f39a7ae65492cccd482515a50142..5aa74c00a61282830d82359eae2b114e2a48b6d9 100644
+--- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
++++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
+@@ -78,6 +78,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ }
+
+ private boolean addEntityUuid(T entity) {
++ org.spigotmc.AsyncCatcher.catchOp("Entity add by UUID"); // Paper
+ if (!this.knownUuids.add(entity.getUUID())) {
+ PersistentEntitySectionManager.LOGGER.warn("UUID of added entity already exists: {}", entity);
+ return false;
+@@ -91,6 +92,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ }
+
+ private boolean addEntity(T entity, boolean existing) {
++ org.spigotmc.AsyncCatcher.catchOp("Entity add"); // Paper
+ // Paper start - chunk system hooks
+ // I don't want to know why this is a generic type.
+ Entity entityCasted = (Entity)entity;
+@@ -144,19 +146,23 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ }
+
+ void startTicking(T entity) {
++ org.spigotmc.AsyncCatcher.catchOp("Entity start ticking"); // Paper
+ this.callbacks.onTickingStart(entity);
+ }
+
+ void stopTicking(T entity) {
++ org.spigotmc.AsyncCatcher.catchOp("Entity stop ticking"); // Paper
+ this.callbacks.onTickingEnd(entity);
+ }
+
+ void startTracking(T entity) {
++ org.spigotmc.AsyncCatcher.catchOp("Entity start tracking"); // Paper
+ this.visibleEntityStorage.add(entity);
+ this.callbacks.onTrackingStart(entity);
+ }
+
+ void stopTracking(T entity) {
++ org.spigotmc.AsyncCatcher.catchOp("Entity stop tracking"); // Paper
+ this.callbacks.onTrackingEnd(entity);
+ this.visibleEntityStorage.remove(entity);
+ }
+@@ -168,6 +174,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ }
+
+ public void updateChunkStatus(ChunkPos chunkPos, Visibility trackingStatus) {
++ org.spigotmc.AsyncCatcher.catchOp("Update chunk status"); // Paper
+ long i = chunkPos.toLong();
+
+ if (trackingStatus == Visibility.HIDDEN) {
+@@ -212,6 +219,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ }
+
+ public void ensureChunkQueuedForLoad(long chunkPos) {
++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk save"); // Paper
+ PersistentEntitySectionManager.ChunkLoadStatus persistententitysectionmanager_b = (PersistentEntitySectionManager.ChunkLoadStatus) this.chunkLoadStatuses.get(chunkPos);
+
+ if (persistententitysectionmanager_b == PersistentEntitySectionManager.ChunkLoadStatus.FRESH) {
+@@ -256,6 +264,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ }
+
+ private void requestChunkLoad(long chunkPos) {
++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk load request"); // Paper
+ this.chunkLoadStatuses.put(chunkPos, PersistentEntitySectionManager.ChunkLoadStatus.PENDING);
+ ChunkPos chunkcoordintpair = new ChunkPos(chunkPos);
+ CompletableFuture completablefuture = this.permanentStorage.loadEntities(chunkcoordintpair);
+@@ -269,6 +278,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ }
+
+ private boolean processChunkUnload(long chunkPos) {
++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk unload process"); // Paper
+ boolean flag = this.storeChunkSections(chunkPos, (entityaccess) -> {
+ entityaccess.getPassengersAndSelf().forEach(this::unloadEntity);
+ }, true); // CraftBukkit - add boolean for event call
+@@ -293,6 +303,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ }
+
+ private void processPendingLoads() {
++ org.spigotmc.AsyncCatcher.catchOp("Entity chunk process pending loads"); // Paper
+ ChunkEntities<T> chunkentities; // CraftBukkit - decompile error
+
+ while ((chunkentities = (ChunkEntities) this.loadingInbox.poll()) != null) {
+@@ -309,6 +320,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ }
+
+ public void tick() {
++ org.spigotmc.AsyncCatcher.catchOp("Entity manager tick"); // Paper
+ this.processPendingLoads();
+ this.processUnloads();
+ }
+@@ -329,6 +341,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ }
+
+ public void autoSave() {
++ org.spigotmc.AsyncCatcher.catchOp("Entity manager autosave"); // Paper
+ this.getAllChunksToSave().forEach((java.util.function.LongConsumer) (i) -> { // CraftBukkit - decompile error
+ boolean flag = this.chunkVisibility.get(i) == Visibility.HIDDEN;
+
+@@ -343,6 +356,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ }
+
+ public void saveAll() {
++ org.spigotmc.AsyncCatcher.catchOp("Entity manager save"); // Paper
+ LongSet longset = this.getAllChunksToSave();
+
+ while (!longset.isEmpty()) {
+@@ -450,6 +464,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+ long i = SectionPos.asLong(blockposition);
+
+ if (i != this.currentSectionKey) {
++ org.spigotmc.AsyncCatcher.catchOp("Entity move"); // Paper
+ Visibility visibility = this.currentSection.getStatus();
+
+ if (!this.currentSection.remove(this.entity)) {
+@@ -504,6 +519,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
+
+ @Override
+ public void onRemove(Entity.RemovalReason reason) {
++ org.spigotmc.AsyncCatcher.catchOp("Entity remove"); // Paper
+ if (!this.currentSection.remove(this.entity)) {
+ PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", new Object[]{this.entity, SectionPos.of(this.currentSectionKey), reason});
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index d650822155f4628ea9d61ecbd4208520746aa59f..0ab6a9496e76aca6815bb8deebb8facd80a6b912 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -1755,6 +1755,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+
+ @Override
+ public void playSound(Location loc, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) {
++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper
+ if (loc == null || sound == null || category == null) return;
+
+ double x = loc.getX();
+@@ -1766,6 +1767,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+
+ @Override
+ public void playSound(Location loc, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) {
++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper
+ if (loc == null || sound == null || category == null) return;
+
+ double x = loc.getX();
+@@ -1798,6 +1800,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+
+ @Override
+ public void playSound(Entity entity, Sound sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) {
++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper
+ if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return;
+
+ ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(CraftSound.bukkitToMinecraftHolder(sound), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed);
+@@ -1818,6 +1821,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+
+ @Override
+ public void playSound(Entity entity, String sound, org.bukkit.SoundCategory category, float volume, float pitch, long seed) {
++ org.spigotmc.AsyncCatcher.catchOp("play sound"); // Paper
+ if (!(entity instanceof CraftEntity craftEntity) || entity.getWorld() != this || sound == null || category == null) return;
+
+ ClientboundSoundEntityPacket packet = new ClientboundSoundEntityPacket(Holder.direct(SoundEvent.createVariableRangeEvent(ResourceLocation.parse(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), craftEntity.getHandle(), volume, pitch, seed);
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+index 5d6e4f2aeca9be4dd4504bc93b006e89ef875931..10a95c1a40597867ffd2974037bfed86dd6deda4 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+@@ -534,6 +534,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+
+ @Override
+ public boolean addPotionEffect(PotionEffect effect, boolean force) {
++ org.spigotmc.AsyncCatcher.catchOp("effect add"); // Paper
+ this.getHandle().addEffect(org.bukkit.craftbukkit.potion.CraftPotionUtil.fromBukkit(effect), EntityPotionEffectEvent.Cause.PLUGIN); // Paper - Don't ignore icon
+ return true;
+ }
+diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java
+index bbf0d9d9c44fe8d7add2f978994ec129420814c7..ef2598760458833021ef1bee92137f42c9fe591f 100644
+--- a/src/main/java/org/spigotmc/AsyncCatcher.java
++++ b/src/main/java/org/spigotmc/AsyncCatcher.java
+@@ -11,6 +11,7 @@ public class AsyncCatcher
+ {
+ if ( AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread )
+ {
++ MinecraftServer.LOGGER.error("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); // Paper
+ throw new IllegalStateException( "Asynchronous " + reason + "!" );
+ }
+ }
diff --git a/patches/server/0591-Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/0591-Add-paper-mobcaps-and-paper-playermobcaps.patch
new file mode 100644
index 0000000000..d04dadb215
--- /dev/null
+++ b/patches/server/0591-Add-paper-mobcaps-and-paper-playermobcaps.patch
@@ -0,0 +1,343 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Mon, 16 Aug 2021 01:31:54 -0500
+Subject: [PATCH] Add '/paper mobcaps' and '/paper playermobcaps'
+
+Add commands to get the mobcaps for a world, as well as the mobcaps for
+each player when per-player mob spawning is enabled.
+
+Also has a hover text on each mob category listing what entity types are
+in said category
+
+diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java
+index cdad0fd5257ae842f83b9c1c98b4565b468d4f54..fd4f37711989431f997d77fb0917f8a9232ce53f 100644
+--- a/src/main/java/io/papermc/paper/command/PaperCommand.java
++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
+@@ -40,6 +40,7 @@ public final class PaperCommand extends Command {
+ commands.put(Set.of("dumpplugins"), new DumpPluginsCommand());
+ commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand());
+ commands.put(Set.of("dumpitem"), new DumpItemCommand());
++ commands.put(Set.of("mobcaps", "playermobcaps"), new MobcapsCommand());
+
+ return commands.entrySet().stream()
+ .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
+diff --git a/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..d3b39d88a72ca25057fd8574d32f28db0d420818
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/command/subcommands/MobcapsCommand.java
+@@ -0,0 +1,229 @@
++package io.papermc.paper.command.subcommands;
++
++import com.google.common.collect.ImmutableMap;
++import io.papermc.paper.command.CommandUtil;
++import io.papermc.paper.command.PaperSubcommand;
++import java.util.ArrayList;
++import java.util.Collections;
++import java.util.List;
++import java.util.Map;
++import java.util.function.ToIntFunction;
++import net.kyori.adventure.text.Component;
++import net.kyori.adventure.text.ComponentLike;
++import net.kyori.adventure.text.JoinConfiguration;
++import net.kyori.adventure.text.TextComponent;
++import net.kyori.adventure.text.format.NamedTextColor;
++import net.kyori.adventure.text.format.TextColor;
++import net.minecraft.core.registries.BuiltInRegistries;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.server.level.ServerPlayer;
++import net.minecraft.world.entity.MobCategory;
++import net.minecraft.world.level.NaturalSpawner;
++import org.bukkit.Bukkit;
++import org.bukkit.World;
++import org.bukkit.command.CommandSender;
++import org.bukkit.craftbukkit.CraftWorld;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.entity.Player;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public final class MobcapsCommand implements PaperSubcommand {
++ static final Map<MobCategory, TextColor> MOB_CATEGORY_COLORS = ImmutableMap.<MobCategory, TextColor>builder()
++ .put(MobCategory.MONSTER, NamedTextColor.RED)
++ .put(MobCategory.CREATURE, NamedTextColor.GREEN)
++ .put(MobCategory.AMBIENT, NamedTextColor.GRAY)
++ .put(MobCategory.AXOLOTLS, TextColor.color(0x7324FF))
++ .put(MobCategory.UNDERGROUND_WATER_CREATURE, TextColor.color(0x3541E6))
++ .put(MobCategory.WATER_CREATURE, TextColor.color(0x006EFF))
++ .put(MobCategory.WATER_AMBIENT, TextColor.color(0x00B3FF))
++ .put(MobCategory.MISC, TextColor.color(0x636363))
++ .build();
++
++ @Override
++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
++ switch (subCommand) {
++ case "mobcaps" -> this.printMobcaps(sender, args);
++ case "playermobcaps" -> this.printPlayerMobcaps(sender, args);
++ }
++ return true;
++ }
++
++ @Override
++ public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
++ return switch (subCommand) {
++ case "mobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestMobcaps(args));
++ case "playermobcaps" -> CommandUtil.getListMatchingLast(sender, args, this.suggestPlayerMobcaps(sender, args));
++ default -> throw new IllegalArgumentException();
++ };
++ }
++
++ private List<String> suggestMobcaps(final String[] args) {
++ if (args.length == 1) {
++ final List<String> worlds = new ArrayList<>(Bukkit.getWorlds().stream().map(World::getName).toList());
++ worlds.add("*");
++ return worlds;
++ }
++
++ return Collections.emptyList();
++ }
++
++ private List<String> suggestPlayerMobcaps(final CommandSender sender, final String[] args) {
++ if (args.length == 1) {
++ final List<String> list = new ArrayList<>();
++ for (final Player player : Bukkit.getOnlinePlayers()) {
++ if (!(sender instanceof Player senderPlayer) || senderPlayer.canSee(player)) {
++ list.add(player.getName());
++ }
++ }
++ return list;
++ }
++
++ return Collections.emptyList();
++ }
++
++ private void printMobcaps(final CommandSender sender, final String[] args) {
++ final List<World> worlds;
++ if (args.length == 0) {
++ if (sender instanceof Player player) {
++ worlds = List.of(player.getWorld());
++ } else {
++ sender.sendMessage(Component.text("Must specify a world! ex: '/paper mobcaps world'", NamedTextColor.RED));
++ return;
++ }
++ } else if (args.length == 1) {
++ final String input = args[0];
++ if (input.equals("*")) {
++ worlds = Bukkit.getWorlds();
++ } else {
++ final @Nullable World world = Bukkit.getWorld(input);
++ if (world == null) {
++ sender.sendMessage(Component.text("'" + input + "' is not a valid world!", NamedTextColor.RED));
++ return;
++ } else {
++ worlds = List.of(world);
++ }
++ }
++ } else {
++ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED));
++ return;
++ }
++
++ for (final World world : worlds) {
++ final ServerLevel level = ((CraftWorld) world).getHandle();
++ final NaturalSpawner.@Nullable SpawnState state = level.getChunkSource().getLastSpawnState();
++
++ final int chunks;
++ if (state == null) {
++ chunks = 0;
++ } else {
++ chunks = state.getSpawnableChunkCount();
++ }
++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(),
++ Component.text("Mobcaps for world: "),
++ Component.text(world.getName(), NamedTextColor.AQUA),
++ Component.text(" (" + chunks + " spawnable chunks)")
++ ));
++
++ sender.sendMessage(createMobcapsComponent(
++ category -> {
++ if (state == null) {
++ return 0;
++ } else {
++ return state.getMobCategoryCounts().getOrDefault(category, 0);
++ }
++ },
++ category -> NaturalSpawner.globalLimitForCategory(level, category, chunks)
++ ));
++ }
++ }
++
++ private void printPlayerMobcaps(final CommandSender sender, final String[] args) {
++ final @Nullable Player player;
++ if (args.length == 0) {
++ if (sender instanceof Player pl) {
++ player = pl;
++ } else {
++ sender.sendMessage(Component.text("Must specify a player! ex: '/paper playermobcount playerName'", NamedTextColor.RED));
++ return;
++ }
++ } else if (args.length == 1) {
++ final String input = args[0];
++ player = Bukkit.getPlayerExact(input);
++ if (player == null) {
++ sender.sendMessage(Component.text("Could not find player named '" + input + "'", NamedTextColor.RED));
++ return;
++ }
++ } else {
++ sender.sendMessage(Component.text("Too many arguments!", NamedTextColor.RED));
++ return;
++ }
++
++ final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle();
++ final ServerLevel level = serverPlayer.serverLevel();
++
++ if (!level.paperConfig().entities.spawning.perPlayerMobSpawns) {
++ sender.sendMessage(Component.text("Use '/paper mobcaps' for worlds where per-player mob spawning is disabled.", NamedTextColor.RED));
++ return;
++ }
++
++ sender.sendMessage(Component.join(JoinConfiguration.noSeparators(), Component.text("Mobcaps for player: "), Component.text(player.getName(), NamedTextColor.GREEN)));
++ sender.sendMessage(createMobcapsComponent(
++ category -> level.chunkSource.chunkMap.getMobCountNear(serverPlayer, category),
++ category -> level.getWorld().getSpawnLimitUnsafe(org.bukkit.craftbukkit.util.CraftSpawnCategory.toBukkit(category))
++ ));
++ }
++
++ private static Component createMobcapsComponent(final ToIntFunction<MobCategory> countGetter, final ToIntFunction<MobCategory> limitGetter) {
++ return MOB_CATEGORY_COLORS.entrySet().stream()
++ .map(entry -> {
++ final MobCategory category = entry.getKey();
++ final TextColor color = entry.getValue();
++
++ final Component categoryHover = Component.join(JoinConfiguration.noSeparators(),
++ Component.text("Entity types in category ", TextColor.color(0xE0E0E0)),
++ Component.text(category.getName(), color),
++ Component.text(':', NamedTextColor.GRAY),
++ Component.newline(),
++ Component.newline(),
++ BuiltInRegistries.ENTITY_TYPE.entrySet().stream()
++ .filter(it -> it.getValue().getCategory() == category)
++ .map(it -> Component.translatable(it.getValue().getDescriptionId()))
++ .collect(Component.toComponent(Component.text(", ", NamedTextColor.GRAY)))
++ );
++
++ final Component categoryComponent = Component.text()
++ .content(" " + category.getName())
++ .color(color)
++ .hoverEvent(categoryHover)
++ .build();
++
++ final TextComponent.Builder builder = Component.text()
++ .append(
++ categoryComponent,
++ Component.text(": ", NamedTextColor.GRAY)
++ );
++ final int limit = limitGetter.applyAsInt(category);
++ if (limit != -1) {
++ builder.append(
++ Component.text(countGetter.applyAsInt(category)),
++ Component.text("/", NamedTextColor.GRAY),
++ Component.text(limit)
++ );
++ } else {
++ builder.append(Component.text()
++ .append(
++ Component.text('n'),
++ Component.text("/", NamedTextColor.GRAY),
++ Component.text('a')
++ )
++ .hoverEvent(Component.text("This category does not naturally spawn.")));
++ }
++ return builder;
++ })
++ .map(ComponentLike::asComponent)
++ .collect(Component.toComponent(Component.newline()));
++ }
++}
+diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+index 606a60fe273974b71ed2bd40be819d848627e777..bf943feca387b77a3154773a59da7190d38d8621 100644
+--- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java
++++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java
+@@ -172,6 +172,16 @@ public final class NaturalSpawner {
+ gameprofilerfiller.pop();
+ }
+
++ // Paper start - Add mobcaps commands
++ public static int globalLimitForCategory(final ServerLevel level, final MobCategory category, final int spawnableChunkCount) {
++ final int categoryLimit = level.getWorld().getSpawnLimitUnsafe(CraftSpawnCategory.toBukkit(category));
++ if (categoryLimit < 1) {
++ return categoryLimit;
++ }
++ return categoryLimit * spawnableChunkCount / NaturalSpawner.MAGIC_NUMBER;
++ }
++ // Paper end - Add mobcaps commands
++
+ public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) {
+ BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk);
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 45d887f4143444321f563cdd7d084b2b9ccf911e..33d9f3778996eedc83064332a2fbbdc7c6a8ba90 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -2334,6 +2334,11 @@ public final class CraftServer implements Server {
+
+ @Override
+ public int getSpawnLimit(SpawnCategory spawnCategory) {
++ // Paper start - Add mobcaps commands
++ return this.getSpawnLimitUnsafe(spawnCategory);
++ }
++ public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) {
++ // Paper end - Add mobcaps commands
+ return this.spawnCategoryLimit.getOrDefault(spawnCategory, -1);
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index 0ab6a9496e76aca6815bb8deebb8facd80a6b912..744e3631cd2d5c157c9b6023ca813e57c6f860d6 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -1713,9 +1713,14 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+ Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null");
+ Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory.%s are not supported", spawnCategory);
+
++ // Paper start - Add mobcaps commands
++ return this.getSpawnLimitUnsafe(spawnCategory);
++ }
++ public final int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) {
+ int limit = this.spawnCategoryLimit.getOrDefault(spawnCategory, -1);
+ if (limit < 0) {
+- limit = this.server.getSpawnLimit(spawnCategory);
++ limit = this.server.getSpawnLimitUnsafe(spawnCategory);
++ // Paper end - Add mobcaps commands
+ }
+ return limit;
+ }
+diff --git a/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..6fdc77caa74845786c78a6ba087062b4d698cb82
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/command/subcommands/MobcapsCommandTest.java
+@@ -0,0 +1,22 @@
++package io.papermc.paper.command.subcommands;
++
++import java.util.HashSet;
++import java.util.Set;
++import net.minecraft.world.entity.MobCategory;
++import org.bukkit.support.environment.Normal;
++import org.junit.jupiter.api.Assertions;
++import org.junit.jupiter.api.Test;
++
++@Normal
++public class MobcapsCommandTest {
++ @Test
++ public void testMobCategoryColors() {
++ final Set<String> missing = new HashSet<>();
++ for (final MobCategory value : MobCategory.values()) {
++ if (!MobcapsCommand.MOB_CATEGORY_COLORS.containsKey(value)) {
++ missing.add(value.getName());
++ }
++ }
++ Assertions.assertTrue(missing.isEmpty(), "MobcapsCommand.MOB_CATEGORY_COLORS map missing TextColors for [" + String.join(", ", missing + "]"));
++ }
++}
diff --git a/patches/server/0592-Sanitize-ResourceLocation-error-logging.patch b/patches/server/0592-Sanitize-ResourceLocation-error-logging.patch
new file mode 100644
index 0000000000..602f0d3b27
--- /dev/null
+++ b/patches/server/0592-Sanitize-ResourceLocation-error-logging.patch
@@ -0,0 +1,28 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Thu, 26 Aug 2021 12:09:47 +0200
+Subject: [PATCH] Sanitize ResourceLocation error logging
+
+
+diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java
+index 262660d115a5d5cbecfbae995955a24283e666b0..87afe84791af2d5e9f869cd4c09eed4bb5fee75b 100644
+--- a/src/main/java/net/minecraft/resources/ResourceLocation.java
++++ b/src/main/java/net/minecraft/resources/ResourceLocation.java
+@@ -247,7 +247,7 @@ public final class ResourceLocation implements Comparable<ResourceLocation> {
+
+ private static String assertValidNamespace(String namespace, String path) {
+ if (!isValidNamespace(namespace)) {
+- throw new ResourceLocationException("Non [a-z0-9_.-] character in namespace of location: " + namespace + ":" + path);
++ throw new ResourceLocationException("Non [a-z0-9_.-] character in namespace of location: " + org.apache.commons.lang3.StringUtils.normalizeSpace(namespace) + ":" + org.apache.commons.lang3.StringUtils.normalizeSpace(path)); // Paper - Sanitize ResourceLocation error logging
+ } else {
+ return namespace;
+ }
+@@ -268,7 +268,7 @@ public final class ResourceLocation implements Comparable<ResourceLocation> {
+
+ private static String assertValidPath(String namespace, String path) {
+ if (!isValidPath(path)) {
+- throw new ResourceLocationException("Non [a-z0-9/._-] character in path of location: " + namespace + ":" + path);
++ throw new ResourceLocationException("Non [a-z0-9/._-] character in path of location: " + namespace + ":" + org.apache.commons.lang3.StringUtils.normalizeSpace(path)); // Paper - Sanitize ResourceLocation error logging
+ } else {
+ return path;
+ }
diff --git a/patches/server/0593-Manually-inline-methods-in-BlockPosition.patch b/patches/server/0593-Manually-inline-methods-in-BlockPosition.patch
new file mode 100644
index 0000000000..67bbeaf54d
--- /dev/null
+++ b/patches/server/0593-Manually-inline-methods-in-BlockPosition.patch
@@ -0,0 +1,63 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Mon, 6 Jul 2020 22:48:48 -0700
+Subject: [PATCH] Manually inline methods in BlockPosition
+
+
+diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
+index 57340f0f46d5429585d99712dd39347d30d9951b..bf3eb9259f97d6d5af01244f103dc6b19c185d50 100644
+--- a/src/main/java/net/minecraft/core/BlockPos.java
++++ b/src/main/java/net/minecraft/core/BlockPos.java
+@@ -588,9 +588,9 @@ public class BlockPos extends Vec3i {
+ }
+
+ public BlockPos.MutableBlockPos set(int x, int y, int z) {
+- this.setX(x);
+- this.setY(y);
+- this.setZ(z);
++ this.x = x; // Paper - Perf: Manually inline methods in BlockPosition
++ this.y = y; // Paper - Perf: Manually inline methods in BlockPosition
++ this.z = z; // Paper - Perf: Manually inline methods in BlockPosition
+ return this;
+ }
+
+@@ -655,19 +655,19 @@ public class BlockPos extends Vec3i {
+
+ @Override
+ public BlockPos.MutableBlockPos setX(int i) {
+- super.setX(i);
++ this.x = i; // Paper - Perf: Manually inline methods in BlockPosition
+ return this;
+ }
+
+ @Override
+ public BlockPos.MutableBlockPos setY(int i) {
+- super.setY(i);
++ this.y = i; // Paper - Perf: Manually inline methods in BlockPosition
+ return this;
+ }
+
+ @Override
+ public BlockPos.MutableBlockPos setZ(int i) {
+- super.setZ(i);
++ this.z = i; // Paper - Perf: Manually inline methods in BlockPosition
+ return this;
+ }
+
+diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java
+index 7d5f99cac756c54e5922bf85d5d359edcc21f1e8..2f2bcc1b9b32e58bf70ae6c171177ceb333ed6cd 100644
+--- a/src/main/java/net/minecraft/core/Vec3i.java
++++ b/src/main/java/net/minecraft/core/Vec3i.java
+@@ -16,9 +16,9 @@ public class Vec3i implements Comparable<Vec3i> {
+ vec -> IntStream.of(vec.getX(), vec.getY(), vec.getZ())
+ );
+ public static final Vec3i ZERO = new Vec3i(0, 0, 0);
+- private int x;
+- private int y;
+- private int z;
++ protected int x; // Paper - Perf: Manually inline methods in BlockPosition; protected
++ protected int y; // Paper - Perf: Manually inline methods in BlockPosition; protected
++ protected int z; // Paper - Perf: Manually inline methods in BlockPosition; protected
+
+ public static Codec<Vec3i> offsetCodec(int maxAbsValue) {
+ return CODEC.validate(
diff --git a/patches/server/0594-Name-craft-scheduler-threads-according-to-the-plugin.patch b/patches/server/0594-Name-craft-scheduler-threads-according-to-the-plugin.patch
new file mode 100644
index 0000000000..30f250f235
--- /dev/null
+++ b/patches/server/0594-Name-craft-scheduler-threads-according-to-the-plugin.patch
@@ -0,0 +1,33 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Sun, 19 Jul 2020 15:17:01 -0700
+Subject: [PATCH] Name craft scheduler threads according to the plugin using
+ them
+
+Provides quick access to culprits running far more threads than
+they should be
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
+index 365b7e7c665bec357fb76d1479bf17da6f603590..e97f6b76ef2fe21c7c2eca8d4a707e5866d70de9 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java
+@@ -25,7 +25,10 @@ class CraftAsyncTask extends CraftTask {
+ @Override
+ public void run() {
+ final Thread thread = Thread.currentThread();
+- synchronized (this.workers) {
++ // Paper start - name threads according to running plugin
++ final String nameBefore = thread.getName();
++ thread.setName(nameBefore + " - " + this.getOwner().getName());
++ try { synchronized (this.workers) { // Paper end - name threads according to running plugin
+ if (this.getPeriod() == CraftTask.CANCEL) {
+ // Never continue running after cancelled.
+ // Checking this with the lock is important!
+@@ -92,6 +95,7 @@ class CraftAsyncTask extends CraftTask {
+ }
+ }
+ }
++ } finally { thread.setName(nameBefore); } // Paper - name threads according to running plugin
+ }
+
+ LinkedList<BukkitWorker> getWorkers() {
diff --git a/patches/server/0595-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch b/patches/server/0595-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch
new file mode 100644
index 0000000000..73d62bea98
--- /dev/null
+++ b/patches/server/0595-Make-sure-inlined-getChunkAt-has-inlined-logic-for-l.patch
@@ -0,0 +1,31 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Sun, 20 Sep 2020 16:10:49 -0700
+Subject: [PATCH] Make sure inlined getChunkAt has inlined logic for loaded
+ chunks
+
+Tux did some profiling some time ago and showed that the
+previous getChunkAt method which had inlined logic for loaded
+chunks did get inlined, but the standard CPS.getChunkAt
+method was not inlined.
+
+diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
+index 68cee32833a3d683852e67e3727d62b84fa60cc4..a1731f09e724794be90dbf47e1d463a10cdb3052 100644
+--- a/src/main/java/net/minecraft/world/level/Level.java
++++ b/src/main/java/net/minecraft/world/level/Level.java
+@@ -351,7 +351,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+
+ @Override
+ public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline
+- return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump
++ // Paper start - Perf: make sure loaded chunks get the inlined variant of this function
++ net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource();
++ LevelChunk ifLoaded = cps.getChunkAtIfLoadedImmediately(chunkX, chunkZ);
++ if (ifLoaded != null) {
++ return ifLoaded;
++ }
++ return (LevelChunk) cps.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump
++ // Paper end - Perf: make sure loaded chunks get the inlined variant of this function
+ }
+
+ // Paper start - if loaded
diff --git a/patches/server/0596-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch b/patches/server/0596-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch
new file mode 100644
index 0000000000..eff1d0a95f
--- /dev/null
+++ b/patches/server/0596-Don-t-read-neighbour-chunk-data-off-disk-when-conver.patch
@@ -0,0 +1,21 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Sun, 11 Apr 2021 02:58:48 -0700
+Subject: [PATCH] Don't read neighbour chunk data off disk when converting
+ chunks
+
+Lighting is purged on update anyways, so let's not add more
+into the conversion process
+
+diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+index 909acbf5b8c6edcb4529647c11464d911d6f8c15..7d5e2e6e96ea9017334dddade54a9dcb37518642 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
++++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java
+@@ -47,6 +47,7 @@ public class ChunkStorage implements AutoCloseable {
+
+ // CraftBukkit start
+ private boolean check(ServerChunkCache cps, int x, int z) {
++ if (true) return true; // Paper - Perf: this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full"
+ ChunkPos pos = new ChunkPos(x, z);
+ if (cps != null) {
+ com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread");
diff --git a/patches/server/0597-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch b/patches/server/0597-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch
new file mode 100644
index 0000000000..1626fb7b50
--- /dev/null
+++ b/patches/server/0597-Don-t-lookup-fluid-state-when-raytracing-skip-air-bl.patch
@@ -0,0 +1,26 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Fri, 28 Aug 2020 12:33:47 -0700
+Subject: [PATCH] Don't lookup fluid state when raytracing, skip air blocks
+
+Just use the iblockdata already retrieved, removes a getType call.
+
+Also save approx. 5% for the raytrace call, as most (expensive)
+raytracing tends to go through air and returning early is an
+easy win. The remaining problems with this function
+are mostly with the block getting itself.
+
+diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java
+index 6bf3019ff06501e82de5976417fd98a10ae6334a..e69473307b30d2b805fc1723ae8f6f2be76310ef 100644
+--- a/src/main/java/net/minecraft/world/level/BlockGetter.java
++++ b/src/main/java/net/minecraft/world/level/BlockGetter.java
+@@ -80,7 +80,8 @@ public interface BlockGetter extends LevelHeightAccessor {
+ return BlockHitResult.miss(raytrace1.getTo(), Direction.getApproximateNearest(vec3d.x, vec3d.y, vec3d.z), BlockPos.containing(raytrace1.getTo()));
+ }
+ // Paper end - Prevent raytrace from loading chunks
+- FluidState fluid = this.getFluidState(blockposition);
++ if (iblockdata.isAir()) return null; // Paper - Perf: optimise air cases
++ FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: don't need to go to world state again
+ Vec3 vec3d = raytrace1.getFrom();
+ Vec3 vec3d1 = raytrace1.getTo();
+ VoxelShape voxelshape = raytrace1.getBlockShape(iblockdata, this, blockposition);
diff --git a/patches/server/0598-Oprimise-map-impl-for-tracked-players.patch b/patches/server/0598-Oprimise-map-impl-for-tracked-players.patch
new file mode 100644
index 0000000000..cea8c3752e
--- /dev/null
+++ b/patches/server/0598-Oprimise-map-impl-for-tracked-players.patch
@@ -0,0 +1,21 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Fri, 19 Feb 2021 22:51:52 -0800
+Subject: [PATCH] Oprimise map impl for tracked players
+
+Reference2BooleanOpenHashMap is going to have
+better lookups than HashMap.
+
+diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
+index 69f54e812794b23e5f54606da86f71163f5f0bbe..2d325c998e40a65af10d6adbb0dc304bea50e3d8 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -1508,7 +1508,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ final Entity entity;
+ private final int range;
+ SectionPos lastSectionPos;
+- public final Set<ServerPlayerConnection> seenBy = Sets.newIdentityHashSet();
++ public final Set<ServerPlayerConnection> seenBy = new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(); // Paper - Perf: optimise map impl
+
+ public TrackedEntity(final Entity entity, final int i, final int j, final boolean flag) {
+ this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit
diff --git a/patches/server/0599-Add-missing-InventoryType.patch b/patches/server/0599-Add-missing-InventoryType.patch
new file mode 100644
index 0000000000..768c150fc1
--- /dev/null
+++ b/patches/server/0599-Add-missing-InventoryType.patch
@@ -0,0 +1,22 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 27 Dec 2023 16:46:07 -0800
+Subject: [PATCH] Add missing InventoryType
+
+Upstream did not add a DECORATED_POT inventory type
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
+index bc5ec42a54401a2275c05f0f506ba89f00c19ec9..c6159c70f7a37b9bffe268b91905ce848d1d2927 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java
+@@ -529,6 +529,10 @@ public class CraftInventory implements Inventory {
+ return InventoryType.COMPOSTER;
+ } else if (this.inventory instanceof JukeboxBlockEntity) {
+ return InventoryType.JUKEBOX;
++ // Paper start
++ } else if (this.inventory instanceof net.minecraft.world.level.block.entity.DecoratedPotBlockEntity) {
++ return org.bukkit.event.inventory.InventoryType.DECORATED_POT;
++ // Paper end
+ } else {
+ return InventoryType.CHEST;
+ }
diff --git a/patches/server/0600-Optimise-BlockSoil-nearby-water-lookup.patch b/patches/server/0600-Optimise-BlockSoil-nearby-water-lookup.patch
new file mode 100644
index 0000000000..4a5d6c5b9d
--- /dev/null
+++ b/patches/server/0600-Optimise-BlockSoil-nearby-water-lookup.patch
@@ -0,0 +1,52 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Thu, 10 Jun 2021 14:36:00 -0700
+Subject: [PATCH] Optimise BlockSoil nearby water lookup
+
+Apparently the abstract block iteration was taking about
+75% of the method call.
+
+diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java
+index a87f8345aa5520a867a8dd769b43526b20b8c16a..c3dba0c2c94f3804338f86621dc42405e380a6b3 100644
+--- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java
+@@ -154,19 +154,28 @@ public class FarmBlock extends Block {
+ }
+
+ private static boolean isNearWater(LevelReader world, BlockPos pos) {
+- Iterator iterator = BlockPos.betweenClosed(pos.offset(-4, 0, -4), pos.offset(4, 1, 4)).iterator();
+-
+- BlockPos blockposition1;
+-
+- do {
+- if (!iterator.hasNext()) {
+- return false;
++ // Paper start - Perf: remove abstract block iteration
++ int xOff = pos.getX();
++ int yOff = pos.getY();
++ int zOff = pos.getZ();
++
++ for (int dz = -4; dz <= 4; ++dz) {
++ int z = dz + zOff;
++ for (int dx = -4; dx <= 4; ++dx) {
++ int x = xOff + dx;
++ for (int dy = 0; dy <= 1; ++dy) {
++ int y = dy + yOff;
++ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)world.getChunk(x >> 4, z >> 4);
++ net.minecraft.world.level.material.FluidState fluid = chunk.getBlockStateFinal(x, y, z).getFluidState();
++ if (fluid.is(FluidTags.WATER)) {
++ return true;
++ }
++ }
+ }
++ }
+
+- blockposition1 = (BlockPos) iterator.next();
+- } while (!world.getFluidState(blockposition1).is(FluidTags.WATER));
+-
+- return true;
++ return false;
++ // Paper end - Perf: remove abstract block iteration
+ }
+
+ @Override
diff --git a/patches/server/0601-Fix-merchant-inventory-not-closing-on-entity-removal.patch b/patches/server/0601-Fix-merchant-inventory-not-closing-on-entity-removal.patch
new file mode 100644
index 0000000000..70a334e012
--- /dev/null
+++ b/patches/server/0601-Fix-merchant-inventory-not-closing-on-entity-removal.patch
@@ -0,0 +1,22 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 2 Sep 2021 00:24:06 -0700
+Subject: [PATCH] Fix merchant inventory not closing on entity removal
+
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index 2ac09e8eece6ff772c94bb1efd01cc1c45194435..3205ef2b0027a2fa7f9ba5ed3437f71f1c6e02b5 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -2309,6 +2309,11 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ // Spigot end
+ // Spigot Start
+ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.InventoryHolder && (!(entity instanceof ServerPlayer) || entity.getRemovalReason() != Entity.RemovalReason.KILLED)) { // SPIGOT-6876: closeInventory clears death message
++ // Paper start - Fix merchant inventory not closing on entity removal
++ if (entity.getBukkitEntity() instanceof org.bukkit.inventory.Merchant merchant && merchant.getTrader() != null) {
++ merchant.getTrader().closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED);
++ }
++ // Paper end - Fix merchant inventory not closing on entity removal
+ for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((org.bukkit.inventory.InventoryHolder) entity.getBukkitEntity()).getInventory().getViewers())) {
+ h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - Inventory close reason
+ }
diff --git a/patches/server/0602-Check-requirement-before-suggesting-root-nodes.patch b/patches/server/0602-Check-requirement-before-suggesting-root-nodes.patch
new file mode 100644
index 0000000000..fce5e326f9
--- /dev/null
+++ b/patches/server/0602-Check-requirement-before-suggesting-root-nodes.patch
@@ -0,0 +1,32 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: stonar96 <[email protected]>
+Date: Sun, 12 Sep 2021 00:14:21 +0200
+Subject: [PATCH] Check requirement before suggesting root nodes
+
+Child nodes are handled by CommandDispatcher#parse checking
+requirements.
+
+Vanilla clients only send ServerboundCommandSuggestionPacket when
+encountering a command node with ASK_SERVER suggestions, however a
+modified client can send this packet whenever it wants.
+
+diff --git a/src/main/java/com/mojang/brigadier/CommandDispatcher.java b/src/main/java/com/mojang/brigadier/CommandDispatcher.java
+index 858e41fba7dc285dd21a93f3918cc5de3887df5f..92848b64a78fce7a92e1657c2da6fc5ee53eea44 100644
+--- a/src/main/java/com/mojang/brigadier/CommandDispatcher.java
++++ b/src/main/java/com/mojang/brigadier/CommandDispatcher.java
+@@ -538,10 +538,14 @@ public class CommandDispatcher<S> {
+ int i = 0;
+ for (final CommandNode<S> node : parent.getChildren()) {
+ CompletableFuture<Suggestions> future = Suggestions.empty();
++ // Paper start - Don't suggest if the requirement isn't met
++ if (parent != this.root || node.canUse(context.getSource())) {
+ try {
+- if (node.canUse(parse.getContext().getSource())) future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, truncatedInputLowerCase, start)); // CraftBukkit
++ future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, truncatedInputLowerCase, start)); // CraftBukkit
+ } catch (final CommandSyntaxException ignored) {
+ }
++ }
++ // Paper end - Don't suggest if the requirement isn't met
+ futures[i++] = future;
+ }
+
diff --git a/patches/server/0603-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch b/patches/server/0603-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch
new file mode 100644
index 0000000000..0330fe314a
--- /dev/null
+++ b/patches/server/0603-Don-t-respond-to-ServerboundCommandSuggestionPacket-.patch
@@ -0,0 +1,23 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: stonar96 <[email protected]>
+Date: Sun, 12 Sep 2021 00:14:21 +0200
+Subject: [PATCH] Don't respond to ServerboundCommandSuggestionPacket when
+ tab-complete is disabled
+
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index dcc87047769094241a0f0b682dc31cc4767ab5e9..d76df6ed588b1109cdebf239884d4c9ce5de47a6 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -758,6 +758,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ return;
+ }
+ // CraftBukkit end
++ // Paper start - Don't suggest if tab-complete is disabled
++ if (org.spigotmc.SpigotConfig.tabComplete < 0) {
++ return;
++ }
++ // Paper end - Don't suggest if tab-complete is disabled
+ // Paper start - AsyncTabCompleteEvent
+ TAB_COMPLETE_EXECUTOR.execute(() -> this.handleCustomCommandSuggestions0(packet));
+ }
diff --git a/patches/server/0604-Add-packet-limiter-config.patch b/patches/server/0604-Add-packet-limiter-config.patch
new file mode 100644
index 0000000000..e49a44446e
--- /dev/null
+++ b/patches/server/0604-Add-packet-limiter-config.patch
@@ -0,0 +1,108 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Fri, 30 Oct 2020 22:37:16 -0700
+Subject: [PATCH] Add packet limiter config
+
+Example config:
+packet-limiter:
+ kick-message: '&cSent too many packets'
+ limits:
+ all:
+ interval: 7.0
+ max-packet-rate: 500.0
+ ServerboundPlaceRecipePacket:
+ interval: 4.0
+ max-packet-rate: 5.0
+ action: DROP
+
+all section refers to all incoming packets, the action for all is
+hard coded to KICK.
+
+For specific limits, the section name is the class's name,
+and an action can be defined: DROP or KICK
+
+If interval or rate are less-than 0, the limit is ignored
+
+diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
+index f5fe87103ce53ba07b43c6c10fc9dafcc5ea4958..72e69f60fb687f102c963b61042b19162a5ec0b2 100644
+--- a/src/main/java/net/minecraft/network/Connection.java
++++ b/src/main/java/net/minecraft/network/Connection.java
+@@ -137,6 +137,22 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ return null;
+ }
+ // Paper end - add utility methods
++ // Paper start - packet limiter
++ protected final Object PACKET_LIMIT_LOCK = new Object();
++ protected final @Nullable io.papermc.paper.util.IntervalledCounter allPacketCounts = io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.isEnabled() ? new io.papermc.paper.util.IntervalledCounter(
++ (long)(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.interval() * 1.0e9)
++ ) : null;
++ protected final java.util.Map<Class<? extends net.minecraft.network.protocol.Packet<?>>, io.papermc.paper.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>();
++
++ private boolean stopReadingPackets;
++ private void killForPacketSpam() {
++ this.sendPacket(new ClientboundDisconnectPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage)), PacketSendListener.thenRun(() -> {
++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.kickMessage));
++ }), true);
++ this.setReadOnly();
++ this.stopReadingPackets = true;
++ }
++ // Paper end - packet limiter
+
+ public Connection(PacketFlow side) {
+ this.receiving = side;
+@@ -215,6 +231,55 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
+ if (packetlistener == null) {
+ throw new IllegalStateException("Received a packet before the packet listener was initialized");
+ } else {
++ // Paper start - packet limiter
++ if (this.stopReadingPackets) {
++ return;
++ }
++ if (this.allPacketCounts != null ||
++ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.containsKey(packet.getClass())) {
++ long time = System.nanoTime();
++ synchronized (PACKET_LIMIT_LOCK) {
++ if (this.allPacketCounts != null) {
++ this.allPacketCounts.updateAndAdd(1, time);
++ if (this.allPacketCounts.getRate() >= io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.allPackets.maxPacketRate()) {
++ this.killForPacketSpam();
++ return;
++ }
++ }
++
++ for (Class<?> check = packet.getClass(); check != Object.class; check = check.getSuperclass()) {
++ io.papermc.paper.configuration.GlobalConfiguration.PacketLimiter.PacketLimit packetSpecificLimit =
++ io.papermc.paper.configuration.GlobalConfiguration.get().packetLimiter.overrides.get(check);
++ if (packetSpecificLimit == null || !packetSpecificLimit.isEnabled()) {
++ continue;
++ }
++ io.papermc.paper.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> {
++ return new io.papermc.paper.util.IntervalledCounter((long)(packetSpecificLimit.interval() * 1.0e9));
++ });
++ counter.updateAndAdd(1, time);
++ if (counter.getRate() >= packetSpecificLimit.maxPacketRate()) {
++ switch (packetSpecificLimit.action()) {
++ case DROP:
++ return;
++ case KICK:
++ String deobfedPacketName = io.papermc.paper.util.ObfHelper.INSTANCE.deobfClassName(check.getName());
++
++ String playerName;
++ if (this.packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl impl) {
++ playerName = impl.getOwner().getName();
++ } else {
++ playerName = this.getLoggableAddress(net.minecraft.server.MinecraftServer.getServer().logIPs());
++ }
++
++ Connection.LOGGER.warn("{} kicked for packet spamming: {}", playerName, deobfedPacketName.substring(deobfedPacketName.lastIndexOf(".") + 1));
++ this.killForPacketSpam();
++ return;
++ }
++ }
++ }
++ }
++ }
++ // Paper end - packet limiter
+ if (packetlistener.shouldHandleMessage(packet)) {
+ try {
+ Connection.genericsFtw(packet, packetlistener);
diff --git a/patches/server/0605-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch b/patches/server/0605-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch
new file mode 100644
index 0000000000..f5c3a81df3
--- /dev/null
+++ b/patches/server/0605-Fix-setPatternColor-on-tropical-fish-bucket-meta.patch
@@ -0,0 +1,72 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Bjarne Koll <[email protected]>
+Date: Sat, 6 Nov 2021 23:15:20 +0100
+Subject: [PATCH] Fix setPatternColor on tropical fish bucket meta
+
+Prior to this commit, the tropical fish bucket meta would set the body
+color to the previous metas pattern colour when updating the pattern
+colour.
+
+This commit hence simply fixes this by using the proper body colour
+value when updating the pattern color.
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java
+index 8169d08c1bccf7c9896bb083eba388f918fac6c9..a514fe98d3d2b65d2cfd029079c69189bcb99c01 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaTropicalFishBucket.java
+@@ -128,7 +128,7 @@ class CraftMetaTropicalFishBucket extends CraftMetaItem implements TropicalFishB
+ if (this.variant == null) {
+ this.variant = 0;
+ }
+- this.variant = CraftTropicalFish.getData(color, this.getPatternColor(), this.getPattern());
++ this.variant = CraftTropicalFish.getData(color, this.getBodyColor(), this.getPattern()); // Paper - properly set tropical fish pattern color without mutating body color
+ }
+
+ @Override
+diff --git a/src/test/java/io/papermc/paper/inventory/CraftMetaTropicalFishBucketTest.java b/src/test/java/io/papermc/paper/inventory/CraftMetaTropicalFishBucketTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..68a557cedbffb41f27ba21096e2ae5eebbf13f5c
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/inventory/CraftMetaTropicalFishBucketTest.java
+@@ -0,0 +1,41 @@
++package io.papermc.paper.inventory;
++
++import org.bukkit.DyeColor;
++import org.bukkit.Material;
++import org.bukkit.entity.TropicalFish;
++import org.bukkit.inventory.ItemStack;
++import org.bukkit.inventory.meta.TropicalFishBucketMeta;
++import org.bukkit.support.environment.AllFeatures;
++import org.junit.jupiter.api.Assertions;
++import org.junit.jupiter.api.Test;
++
++@AllFeatures
++public class CraftMetaTropicalFishBucketTest {
++
++ @Test
++ public void testAllCombinations() {
++ final var rawMeta = new ItemStack(Material.TROPICAL_FISH_BUCKET).getItemMeta();
++ Assertions.assertTrue(rawMeta instanceof TropicalFishBucketMeta, "Meta was not a tropical fish bucket");
++
++ final var meta = (TropicalFishBucketMeta) rawMeta;
++
++ for (final var bodyColor : DyeColor.values()) {
++ for (final var pattern : TropicalFish.Pattern.values()) {
++ for (final var patternColor : DyeColor.values()) {
++ meta.setBodyColor(bodyColor);
++ Assertions.assertEquals(bodyColor, meta.getBodyColor(), "Body color did not match post body color!");
++
++ meta.setPattern(pattern);
++ Assertions.assertEquals(pattern, meta.getPattern(), "Pattern did not match post pattern!");
++ Assertions.assertEquals(bodyColor, meta.getBodyColor(), "Body color did not match post pattern!");
++
++ meta.setPatternColor(patternColor);
++ Assertions.assertEquals(pattern, meta.getPattern(), "Pattern did not match post pattern color!");
++ Assertions.assertEquals(bodyColor, meta.getBodyColor(), "Body color did not match post pattern color!");
++ Assertions.assertEquals(patternColor, meta.getPatternColor(), "Pattern color did not match post pattern color!");
++ }
++ }
++ }
++ }
++
++}
diff --git a/patches/server/0606-Ensure-valid-vehicle-status.patch b/patches/server/0606-Ensure-valid-vehicle-status.patch
new file mode 100644
index 0000000000..88fa596782
--- /dev/null
+++ b/patches/server/0606-Ensure-valid-vehicle-status.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Tue, 28 Sep 2021 09:47:47 +0200
+Subject: [PATCH] Ensure valid vehicle status
+
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index 215be207e29254312bb85f259a684a7de6876aa9..e2a8e0291a5c0c00d7d6135a0d34fb8bab7b4cd5 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -696,7 +696,7 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ }
+ }
+
+- if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger()) {
++ if (persistVehicle && entity1 != null && entity != this && entity.hasExactlyOnePlayerPassenger() && !entity.isRemoved()) { // Paper - Ensure valid vehicle status
+ // CraftBukkit end
+ CompoundTag nbttagcompound1 = new CompoundTag();
+ CompoundTag nbttagcompound2 = new CompoundTag();
diff --git a/patches/server/0607-Prevent-softlocked-end-exit-portal-generation.patch b/patches/server/0607-Prevent-softlocked-end-exit-portal-generation.patch
new file mode 100644
index 0000000000..4c54f27f94
--- /dev/null
+++ b/patches/server/0607-Prevent-softlocked-end-exit-portal-generation.patch
@@ -0,0 +1,22 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Noah van der Aa <[email protected]>
+Date: Mon, 30 Aug 2021 15:22:18 +0200
+Subject: [PATCH] Prevent softlocked end exit portal generation
+
+
+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 3d1a49d865e17a61ff74c6fe0efbd908447ee730..bc6426c04ac1fd19949d587d2b7061895db0893b 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
+@@ -469,6 +469,11 @@ public class EndDragonFight {
+ }
+ }
+
++ // Paper start - Prevent "softlocked" exit portal generation
++ if (this.portalLocation.getY() <= this.level.getMinY()) {
++ this.portalLocation = this.portalLocation.atY(this.level.getMinY() + 1);
++ }
++ // Paper end - Prevent "softlocked" exit portal generation
+ if (worldgenendtrophy.place(FeatureConfiguration.NONE, this.level, this.level.getChunkSource().getGenerator(), RandomSource.create(), this.portalLocation)) {
+ int i = Mth.positiveCeilDiv(4, 16);
+
diff --git a/patches/server/0608-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch b/patches/server/0608-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch
new file mode 100644
index 0000000000..a145d67c1a
--- /dev/null
+++ b/patches/server/0608-Fix-CocaoDecorator-causing-a-crash-when-trying-to-ge.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Shane Freeder <[email protected]>
+Date: Tue, 7 Sep 2021 21:29:38 +0100
+Subject: [PATCH] Fix CocaoDecorator causing a crash when trying to generate
+ without logs
+
+
+diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java b/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java
+index 3acae3afbfc7c15442ddde7aaf250dac5be2bde5..a6c6d0fe89af4265d0034b8d03f4fdb8d5c1a04f 100644
+--- a/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java
++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/treedecorators/CocoaDecorator.java
+@@ -26,6 +26,7 @@ public class CocoaDecorator extends TreeDecorator {
+
+ @Override
+ public void place(TreeDecorator.Context generator) {
++ if (generator.logs().isEmpty()) return; // Paper - Fix crash when trying to generate without logs
+ RandomSource randomSource = generator.random();
+ if (!(randomSource.nextFloat() >= this.probability)) {
+ List<BlockPos> list = generator.logs();
diff --git a/patches/server/0609-Don-t-log-debug-logging-being-disabled.patch b/patches/server/0609-Don-t-log-debug-logging-being-disabled.patch
new file mode 100644
index 0000000000..8e733b1cea
--- /dev/null
+++ b/patches/server/0609-Don-t-log-debug-logging-being-disabled.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Noah van der Aa <[email protected]>
+Date: Tue, 14 Sep 2021 16:24:45 +0200
+Subject: [PATCH] Don't log debug logging being disabled
+
+
+diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java
+index 85f3433860abd91a89961907940a807a8b190a46..4dbb109d0526afee99b9190fc256585121aac9b5 100644
+--- a/src/main/java/org/spigotmc/SpigotConfig.java
++++ b/src/main/java/org/spigotmc/SpigotConfig.java
+@@ -381,7 +381,7 @@ public class SpigotConfig
+ Bukkit.getLogger().info( "Debug logging is enabled" );
+ } else
+ {
+- Bukkit.getLogger().info( "Debug logging is disabled" );
++ // Bukkit.getLogger().info( "Debug logging is disabled" ); // Paper - Don't log if debug logging isn't enabled.
+ }
+ }
+
diff --git a/patches/server/0610-fix-various-menus-with-empty-level-accesses.patch b/patches/server/0610-fix-various-menus-with-empty-level-accesses.patch
new file mode 100644
index 0000000000..efe5d1ddce
--- /dev/null
+++ b/patches/server/0610-fix-various-menus-with-empty-level-accesses.patch
@@ -0,0 +1,23 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 11 Jul 2021 12:52:56 -0700
+Subject: [PATCH] fix various menus with empty level accesses
+
+
+diff --git a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java
+index c96b04f045dda384cdee9254a1765ef97e5f7f03..85e336637db8643fc5aca1dba724c9b341cbf46f 100644
+--- a/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java
++++ b/src/main/java/net/minecraft/world/inventory/ContainerLevelAccess.java
+@@ -27,6 +27,12 @@ public interface ContainerLevelAccess {
+ public <T> Optional<T> evaluate(BiFunction<Level, BlockPos, T> getter) {
+ return Optional.empty();
+ }
++ // Paper start - fix menus with empty level accesses
++ @Override
++ public org.bukkit.Location getLocation() {
++ return null;
++ }
++ // Paper end - fix menus with empty level accesses
+ };
+
+ static ContainerLevelAccess create(final Level world, final BlockPos pos) {
diff --git a/patches/server/0611-Preserve-overstacked-loot.patch b/patches/server/0611-Preserve-overstacked-loot.patch
new file mode 100644
index 0000000000..db50d2fd8a
--- /dev/null
+++ b/patches/server/0611-Preserve-overstacked-loot.patch
@@ -0,0 +1,27 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: lexikiq <[email protected]>
+Date: Mon, 21 Jun 2021 23:21:58 -0400
+Subject: [PATCH] Preserve overstacked loot
+
+Preserves overstacked items in loot tables, such as shulker box drops, to prevent the items
+from being deleted (as they'd overflow past the bounds of the container)-- or worse, causing
+chunk bans via the large amount of NBT created by unstacking the items.
+
+Fixes GH-5140 and GH-4748.
+
+diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java
+index 4d4d413b8527e1a109276928611b8c857ad6f6aa..c2bded5094097f5615a2ddb0718942486ede93b5 100644
+--- a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java
++++ b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java
+@@ -72,9 +72,10 @@ public class LootTable {
+ }
+
+ public static Consumer<ItemStack> createStackSplitter(ServerLevel world, Consumer<ItemStack> consumer) {
++ boolean skipSplitter = world != null && !world.paperConfig().fixes.splitOverstackedLoot; // Paper - preserve overstacked items
+ return (itemstack) -> {
+ if (itemstack.isItemEnabled(world.enabledFeatures())) {
+- if (itemstack.getCount() < itemstack.getMaxStackSize()) {
++ if (skipSplitter || itemstack.getCount() < itemstack.getMaxStackSize()) { // Paper - preserve overstacked items
+ consumer.accept(itemstack);
+ } else {
+ int i = itemstack.getCount();
diff --git a/patches/server/0612-Update-head-rotation-in-missing-places.patch b/patches/server/0612-Update-head-rotation-in-missing-places.patch
new file mode 100644
index 0000000000..d9477bbb64
--- /dev/null
+++ b/patches/server/0612-Update-head-rotation-in-missing-places.patch
@@ -0,0 +1,29 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Mon, 21 Jun 2021 21:55:23 -0400
+Subject: [PATCH] Update head rotation in missing places
+
+In certain areas the player's head rotation could be desynced when teleported/moved.
+This is because bukkit uses a separate head rotation field for yaw.
+This issue only applies to players.
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index ee2625301ebb1dd601618ae43915891f3b57b7c3..9d95be758a905209eb1de9ee9d3b0f5af5b14074 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -1938,6 +1938,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F);
+ this.yRotO = this.getYRot();
+ this.xRotO = this.getXRot();
++ this.setYHeadRot(yaw); // Paper - Update head rotation
+ }
+
+ public void absMoveTo(double x, double y, double z) {
+@@ -1980,6 +1981,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ this.setXRot(pitch);
+ this.setOldPosAndRot();
+ this.reapplyPosition();
++ this.setYHeadRot(yaw); // Paper - Update head rotation
+ }
+
+ public final void setOldPosAndRot() {
diff --git a/patches/server/0613-prevent-unintended-light-block-manipulation.patch b/patches/server/0613-prevent-unintended-light-block-manipulation.patch
new file mode 100644
index 0000000000..e9851e3744
--- /dev/null
+++ b/patches/server/0613-prevent-unintended-light-block-manipulation.patch
@@ -0,0 +1,25 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Mon, 13 Sep 2021 18:55:45 -0700
+Subject: [PATCH] prevent unintended light block manipulation
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/LightBlock.java b/src/main/java/net/minecraft/world/level/block/LightBlock.java
+index 2be8855b2cd50e599d80e1ff5dc13d66451dff9d..9f6bb23b6021a99d4bb09d57659943cfdb4b673f 100644
+--- a/src/main/java/net/minecraft/world/level/block/LightBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/LightBlock.java
+@@ -50,6 +50,14 @@ public class LightBlock extends Block implements SimpleWaterloggedBlock {
+ builder.add(LEVEL, WATERLOGGED);
+ }
+
++ // Paper start - prevent unintended light block manipulation
++ @Override
++ protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level world, BlockPos pos, Player player, net.minecraft.world.InteractionHand hand, BlockHitResult hit) {
++ if (player.getItemInHand(hand).getItem() != Items.LIGHT || (world instanceof final net.minecraft.server.level.ServerLevel serverLevel && !player.mayInteract(serverLevel, pos)) || !player.mayUseItemAt(pos, hit.getDirection(), player.getItemInHand(hand))) { return net.minecraft.world.InteractionResult.PASS; } // Paper - Prevent unintended light block manipulation
++ return super.useItemOn(stack, state, world, pos, player, hand, hit);
++ }
++ // Paper end - prevent unintended light block manipulation
++
+ @Override
+ protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
+ if (!world.isClientSide && player.canUseGameMasterBlocks()) {
diff --git a/patches/server/0614-Fix-CraftCriteria-defaults-map.patch b/patches/server/0614-Fix-CraftCriteria-defaults-map.patch
new file mode 100644
index 0000000000..3e79bd7fb7
--- /dev/null
+++ b/patches/server/0614-Fix-CraftCriteria-defaults-map.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Mon, 4 Oct 2021 22:31:51 -0700
+Subject: [PATCH] Fix CraftCriteria defaults map
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java
+index 441e2122f837712a21328eb7659cc9925ff9b6f8..8464531a4ee400834d25c23b1eb723f49be8689e 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java
++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftCriteria.java
+@@ -54,7 +54,7 @@ public final class CraftCriteria implements Criteria {
+ }
+
+ static CraftCriteria getFromNMS(Objective objective) {
+- return CraftCriteria.DEFAULTS.get(objective.getCriteria().getName());
++ return java.util.Objects.requireNonNullElseGet(CraftCriteria.DEFAULTS.get(objective.getCriteria().getName()), () -> new CraftCriteria(objective.getCriteria())); // Paper
+ }
+
+ public static CraftCriteria getFromBukkit(String name) {
diff --git a/patches/server/0615-Fix-upstreams-block-state-factories.patch b/patches/server/0615-Fix-upstreams-block-state-factories.patch
new file mode 100644
index 0000000000..d5198201c5
--- /dev/null
+++ b/patches/server/0615-Fix-upstreams-block-state-factories.patch
@@ -0,0 +1,491 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 6 Oct 2021 20:50:48 -0700
+Subject: [PATCH] Fix upstreams block state factories
+
+Sometimes, blocks are changed and then logic is called before the associated
+tile entity is removed. When this happens, the factories were relying on the
+block at the position, not the tile entity. This change prioritizes using the
+tile entity type to determine the block state factory and falls back on
+the material type of the block at that location.
+
+== AT ==
+public net.minecraft.world.level.block.entity.BlockEntityType validBlocks
+
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
+index 8e091ac441acf88a5c8597c6d2923efa5662092e..b1ed43287d522e08a967ba751a851776351916e7 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/BlockEntity.java
+@@ -370,7 +370,7 @@ public abstract class BlockEntity {
+ // Paper end
+ if (this.level == null) return null;
+ org.bukkit.block.Block block = this.level.getWorld().getBlockAt(this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ());
+- if (block.getType() == org.bukkit.Material.AIR) return null;
++ // if (block.getType() == org.bukkit.Material.AIR) return null; // Paper - actually get the tile entity if it still exists
+ org.bukkit.block.BlockState state = block.getState(useSnapshot); // Paper
+ if (state instanceof InventoryHolder) return (InventoryHolder) state;
+ return null;
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
+index 80bd6e8a6dadb74356a9fa9aa394efbd31c49c37..fe7e3e0628783d8d1be9635b689da8a9cb46c5d7 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockEntityState.java
+@@ -20,7 +20,7 @@ import org.bukkit.persistence.PersistentDataContainer;
+ import org.jetbrains.annotations.NotNull;
+ import org.jetbrains.annotations.Nullable;
+
+-public class CraftBlockEntityState<T extends BlockEntity> extends CraftBlockState implements TileState {
++public abstract class CraftBlockEntityState<T extends BlockEntity> extends CraftBlockState implements TileState { // Paper - revert upstream's revert of the block state changes
+
+ private final T tileEntity;
+ private final T snapshot;
+@@ -196,14 +196,10 @@ public class CraftBlockEntityState<T extends BlockEntity> extends CraftBlockStat
+ }
+
+ @Override
+- public CraftBlockEntityState<T> copy() {
+- return new CraftBlockEntityState<>(this, null);
+- }
++ public abstract CraftBlockEntityState<T> copy(); // Paper - make abstract
+
+ @Override
+- public CraftBlockEntityState<T> copy(Location location) {
+- return new CraftBlockEntityState<>(this, location);
+- }
++ public abstract CraftBlockEntityState<T> copy(Location location); // Paper - make abstract
+
+ // Paper start
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
+index 1a8dcde39a252a45046866349b848d79e1b13260..56453454cbd4b9e9270fc833f8ab38d5fa7a3763 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlockStates.java
+@@ -22,6 +22,7 @@ import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
+ import net.minecraft.world.level.block.entity.BellBlockEntity;
+ import net.minecraft.world.level.block.entity.BlastFurnaceBlockEntity;
+ import net.minecraft.world.level.block.entity.BlockEntity;
++import net.minecraft.world.level.block.entity.BlockEntityType; // Paper
+ import net.minecraft.world.level.block.entity.BrewingStandBlockEntity;
+ import net.minecraft.world.level.block.entity.BrushableBlockEntity;
+ import net.minecraft.world.level.block.entity.CalibratedSculkSensorBlockEntity;
+@@ -88,9 +89,9 @@ public final class CraftBlockStates {
+ private static class BlockEntityStateFactory<T extends BlockEntity, B extends CraftBlockEntityState<T>> extends BlockStateFactory<B> {
+
+ private final BiFunction<World, T, B> blockStateConstructor;
+- private final BiFunction<BlockPos, net.minecraft.world.level.block.state.BlockState, T> tileEntityConstructor;
++ private final BlockEntityType<? extends T> tileEntityConstructor; // Paper
+
+- protected BlockEntityStateFactory(Class<B> blockStateType, BiFunction<World, T, B> blockStateConstructor, BiFunction<BlockPos, net.minecraft.world.level.block.state.BlockState, T> tileEntityConstructor) {
++ protected BlockEntityStateFactory(Class<B> blockStateType, BiFunction<World, T, B> blockStateConstructor, BlockEntityType<? extends T> tileEntityConstructor) { // Paper
+ super(blockStateType);
+ this.blockStateConstructor = blockStateConstructor;
+ this.tileEntityConstructor = tileEntityConstructor;
+@@ -107,7 +108,7 @@ public final class CraftBlockStates {
+ }
+
+ private T createTileEntity(BlockPos blockPosition, net.minecraft.world.level.block.state.BlockState blockData) {
+- return this.tileEntityConstructor.apply(blockPosition, blockData);
++ return this.tileEntityConstructor.create(blockPosition, blockData); // Paper
+ }
+
+ private B createBlockState(World world, T tileEntity) {
+@@ -119,233 +120,66 @@ public final class CraftBlockStates {
+ private static final BlockStateFactory<?> DEFAULT_FACTORY = new BlockStateFactory<CraftBlockState>(CraftBlockState.class) {
+ @Override
+ public CraftBlockState createBlockState(World world, BlockPos blockPosition, net.minecraft.world.level.block.state.BlockState blockData, BlockEntity tileEntity) {
+- // SPIGOT-6754, SPIGOT-6817: Restore previous behaviour for tile entities with removed blocks (loot generation post-destroy)
+- if (tileEntity != null) {
+- // block with unhandled TileEntity:
+- return new CraftBlockEntityState<>(world, tileEntity);
+- }
++ // Paper - revert upstream's revert of the block state changes. Block entities that have already had the block type set to AIR are still valid, upstream decided to ignore them
+ Preconditions.checkState(tileEntity == null, "Unexpected BlockState for %s", CraftBlockType.minecraftToBukkit(blockData.getBlock()));
+ return new CraftBlockState(world, blockPosition, blockData);
+ }
+ };
++ // Paper start
++ private static final Map<BlockEntityType<?>, BlockStateFactory<?>> FACTORIES_BY_BLOCK_ENTITY_TYPE = new HashMap<>();
++ private static void register(BlockEntityType<?> type, BlockStateFactory<?> factory) {
++ FACTORIES_BY_BLOCK_ENTITY_TYPE.put(type, factory);
++ }
++ // Paper end
+
+ static {
+- register(
+- Arrays.asList(
+- Material.ACACIA_SIGN,
+- Material.ACACIA_WALL_SIGN,
+- Material.BAMBOO_SIGN,
+- Material.BAMBOO_WALL_SIGN,
+- Material.BIRCH_SIGN,
+- Material.BIRCH_WALL_SIGN,
+- Material.CHERRY_SIGN,
+- Material.CHERRY_WALL_SIGN,
+- Material.CRIMSON_SIGN,
+- Material.CRIMSON_WALL_SIGN,
+- Material.DARK_OAK_SIGN,
+- Material.DARK_OAK_WALL_SIGN,
+- Material.JUNGLE_SIGN,
+- Material.JUNGLE_WALL_SIGN,
+- Material.MANGROVE_SIGN,
+- Material.MANGROVE_WALL_SIGN,
+- Material.OAK_SIGN,
+- Material.OAK_WALL_SIGN,
+- Material.PALE_OAK_SIGN,
+- Material.PALE_OAK_WALL_SIGN,
+- Material.SPRUCE_SIGN,
+- Material.SPRUCE_WALL_SIGN,
+- Material.WARPED_SIGN,
+- Material.WARPED_WALL_SIGN
+- ), CraftSign.class, CraftSign::new, SignBlockEntity::new
+- );
+-
+- register(
+- Arrays.asList(
+- Material.ACACIA_HANGING_SIGN,
+- Material.ACACIA_WALL_HANGING_SIGN,
+- Material.BAMBOO_HANGING_SIGN,
+- Material.BAMBOO_WALL_HANGING_SIGN,
+- Material.BIRCH_HANGING_SIGN,
+- Material.BIRCH_WALL_HANGING_SIGN,
+- Material.CHERRY_HANGING_SIGN,
+- Material.CHERRY_WALL_HANGING_SIGN,
+- Material.CRIMSON_HANGING_SIGN,
+- Material.CRIMSON_WALL_HANGING_SIGN,
+- Material.DARK_OAK_HANGING_SIGN,
+- Material.DARK_OAK_WALL_HANGING_SIGN,
+- Material.JUNGLE_HANGING_SIGN,
+- Material.JUNGLE_WALL_HANGING_SIGN,
+- Material.MANGROVE_HANGING_SIGN,
+- Material.MANGROVE_WALL_HANGING_SIGN,
+- Material.OAK_HANGING_SIGN,
+- Material.OAK_WALL_HANGING_SIGN,
+- Material.PALE_OAK_HANGING_SIGN,
+- Material.PALE_OAK_WALL_HANGING_SIGN,
+- Material.SPRUCE_HANGING_SIGN,
+- Material.SPRUCE_WALL_HANGING_SIGN,
+- Material.WARPED_HANGING_SIGN,
+- Material.WARPED_WALL_HANGING_SIGN
+- ), CraftHangingSign.class, CraftHangingSign::new, HangingSignBlockEntity::new
+- );
+-
+- register(
+- Arrays.asList(
+- Material.CREEPER_HEAD,
+- Material.CREEPER_WALL_HEAD,
+- Material.DRAGON_HEAD,
+- Material.DRAGON_WALL_HEAD,
+- Material.PIGLIN_HEAD,
+- Material.PIGLIN_WALL_HEAD,
+- Material.PLAYER_HEAD,
+- Material.PLAYER_WALL_HEAD,
+- Material.SKELETON_SKULL,
+- Material.SKELETON_WALL_SKULL,
+- Material.WITHER_SKELETON_SKULL,
+- Material.WITHER_SKELETON_WALL_SKULL,
+- Material.ZOMBIE_HEAD,
+- Material.ZOMBIE_WALL_HEAD
+- ), CraftSkull.class, CraftSkull::new, SkullBlockEntity::new
+- );
+-
+- register(
+- Arrays.asList(
+- Material.COMMAND_BLOCK,
+- Material.REPEATING_COMMAND_BLOCK,
+- Material.CHAIN_COMMAND_BLOCK
+- ), CraftCommandBlock.class, CraftCommandBlock::new, CommandBlockEntity::new
+- );
+-
+- register(
+- Arrays.asList(
+- Material.BLACK_BANNER,
+- Material.BLACK_WALL_BANNER,
+- Material.BLUE_BANNER,
+- Material.BLUE_WALL_BANNER,
+- Material.BROWN_BANNER,
+- Material.BROWN_WALL_BANNER,
+- Material.CYAN_BANNER,
+- Material.CYAN_WALL_BANNER,
+- Material.GRAY_BANNER,
+- Material.GRAY_WALL_BANNER,
+- Material.GREEN_BANNER,
+- Material.GREEN_WALL_BANNER,
+- Material.LIGHT_BLUE_BANNER,
+- Material.LIGHT_BLUE_WALL_BANNER,
+- Material.LIGHT_GRAY_BANNER,
+- Material.LIGHT_GRAY_WALL_BANNER,
+- Material.LIME_BANNER,
+- Material.LIME_WALL_BANNER,
+- Material.MAGENTA_BANNER,
+- Material.MAGENTA_WALL_BANNER,
+- Material.ORANGE_BANNER,
+- Material.ORANGE_WALL_BANNER,
+- Material.PINK_BANNER,
+- Material.PINK_WALL_BANNER,
+- Material.PURPLE_BANNER,
+- Material.PURPLE_WALL_BANNER,
+- Material.RED_BANNER,
+- Material.RED_WALL_BANNER,
+- Material.WHITE_BANNER,
+- Material.WHITE_WALL_BANNER,
+- Material.YELLOW_BANNER,
+- Material.YELLOW_WALL_BANNER
+- ), CraftBanner.class, CraftBanner::new, BannerBlockEntity::new
+- );
+-
+- register(
+- Arrays.asList(
+- Material.SHULKER_BOX,
+- Material.WHITE_SHULKER_BOX,
+- Material.ORANGE_SHULKER_BOX,
+- Material.MAGENTA_SHULKER_BOX,
+- Material.LIGHT_BLUE_SHULKER_BOX,
+- Material.YELLOW_SHULKER_BOX,
+- Material.LIME_SHULKER_BOX,
+- Material.PINK_SHULKER_BOX,
+- Material.GRAY_SHULKER_BOX,
+- Material.LIGHT_GRAY_SHULKER_BOX,
+- Material.CYAN_SHULKER_BOX,
+- Material.PURPLE_SHULKER_BOX,
+- Material.BLUE_SHULKER_BOX,
+- Material.BROWN_SHULKER_BOX,
+- Material.GREEN_SHULKER_BOX,
+- Material.RED_SHULKER_BOX,
+- Material.BLACK_SHULKER_BOX
+- ), CraftShulkerBox.class, CraftShulkerBox::new, ShulkerBoxBlockEntity::new
+- );
+-
+- register(
+- Arrays.asList(
+- Material.BLACK_BED,
+- Material.BLUE_BED,
+- Material.BROWN_BED,
+- Material.CYAN_BED,
+- Material.GRAY_BED,
+- Material.GREEN_BED,
+- Material.LIGHT_BLUE_BED,
+- Material.LIGHT_GRAY_BED,
+- Material.LIME_BED,
+- Material.MAGENTA_BED,
+- Material.ORANGE_BED,
+- Material.PINK_BED,
+- Material.PURPLE_BED,
+- Material.RED_BED,
+- Material.WHITE_BED,
+- Material.YELLOW_BED
+- ), CraftBed.class, CraftBed::new, BedBlockEntity::new
+- );
+-
+- register(
+- Arrays.asList(
+- Material.BEEHIVE,
+- Material.BEE_NEST
+- ), CraftBeehive.class, CraftBeehive::new, BeehiveBlockEntity::new
+- );
+-
+- register(
+- Arrays.asList(
+- Material.CAMPFIRE,
+- Material.SOUL_CAMPFIRE
+- ), CraftCampfire.class, CraftCampfire::new, CampfireBlockEntity::new
+- );
+-
+- register(Material.BARREL, CraftBarrel.class, CraftBarrel::new, BarrelBlockEntity::new);
+- register(Material.BEACON, CraftBeacon.class, CraftBeacon::new, BeaconBlockEntity::new);
+- register(Material.BELL, CraftBell.class, CraftBell::new, BellBlockEntity::new);
+- register(Material.BLAST_FURNACE, CraftBlastFurnace.class, CraftBlastFurnace::new, BlastFurnaceBlockEntity::new);
+- register(Material.BREWING_STAND, CraftBrewingStand.class, CraftBrewingStand::new, BrewingStandBlockEntity::new);
+- register(Material.CHEST, CraftChest.class, CraftChest::new, ChestBlockEntity::new);
+- register(Material.CHISELED_BOOKSHELF, CraftChiseledBookshelf.class, CraftChiseledBookshelf::new, ChiseledBookShelfBlockEntity::new);
+- register(Material.COMPARATOR, CraftComparator.class, CraftComparator::new, ComparatorBlockEntity::new);
+- register(Material.CONDUIT, CraftConduit.class, CraftConduit::new, ConduitBlockEntity::new);
+- register(Material.CREAKING_HEART, CraftCreakingHeart.class, CraftCreakingHeart::new, CreakingHeartBlockEntity::new);
+- register(Material.DAYLIGHT_DETECTOR, CraftDaylightDetector.class, CraftDaylightDetector::new, DaylightDetectorBlockEntity::new);
+- register(Material.DECORATED_POT, CraftDecoratedPot.class, CraftDecoratedPot::new, DecoratedPotBlockEntity::new);
+- register(Material.DISPENSER, CraftDispenser.class, CraftDispenser::new, DispenserBlockEntity::new);
+- register(Material.DROPPER, CraftDropper.class, CraftDropper::new, DropperBlockEntity::new);
+- register(Material.ENCHANTING_TABLE, CraftEnchantingTable.class, CraftEnchantingTable::new, EnchantingTableBlockEntity::new);
+- register(Material.ENDER_CHEST, CraftEnderChest.class, CraftEnderChest::new, EnderChestBlockEntity::new);
+- register(Material.END_GATEWAY, CraftEndGateway.class, CraftEndGateway::new, TheEndGatewayBlockEntity::new);
+- register(Material.END_PORTAL, CraftEndPortal.class, CraftEndPortal::new, TheEndPortalBlockEntity::new);
+- register(Material.FURNACE, CraftFurnaceFurnace.class, CraftFurnaceFurnace::new, FurnaceBlockEntity::new);
+- register(Material.HOPPER, CraftHopper.class, CraftHopper::new, HopperBlockEntity::new);
+- register(Material.JIGSAW, CraftJigsaw.class, CraftJigsaw::new, JigsawBlockEntity::new);
+- register(Material.JUKEBOX, CraftJukebox.class, CraftJukebox::new, JukeboxBlockEntity::new);
+- register(Material.LECTERN, CraftLectern.class, CraftLectern::new, LecternBlockEntity::new);
+- register(Material.MOVING_PISTON, CraftMovingPiston.class, CraftMovingPiston::new, PistonMovingBlockEntity::new);
+- register(Material.SCULK_CATALYST, CraftSculkCatalyst.class, CraftSculkCatalyst::new, SculkCatalystBlockEntity::new);
+- register(Material.CALIBRATED_SCULK_SENSOR, CraftCalibratedSculkSensor.class, CraftCalibratedSculkSensor::new, CalibratedSculkSensorBlockEntity::new);
+- register(Material.SCULK_SENSOR, CraftSculkSensor.class, CraftSculkSensor::new, SculkSensorBlockEntity::new);
+- register(Material.SCULK_SHRIEKER, CraftSculkShrieker.class, CraftSculkShrieker::new, SculkShriekerBlockEntity::new);
+- register(Material.SMOKER, CraftSmoker.class, CraftSmoker::new, SmokerBlockEntity::new);
+- register(Material.SPAWNER, CraftCreatureSpawner.class, CraftCreatureSpawner::new, SpawnerBlockEntity::new);
+- register(Material.STRUCTURE_BLOCK, CraftStructureBlock.class, CraftStructureBlock::new, StructureBlockEntity::new);
+- register(Material.SUSPICIOUS_SAND, CraftSuspiciousSand.class, CraftSuspiciousSand::new, BrushableBlockEntity::new);
+- register(Material.SUSPICIOUS_GRAVEL, CraftBrushableBlock.class, CraftBrushableBlock::new, BrushableBlockEntity::new);
+- register(Material.TRAPPED_CHEST, CraftChest.class, CraftChest::new, TrappedChestBlockEntity::new);
+- register(Material.CRAFTER, CraftCrafter.class, CraftCrafter::new, CrafterBlockEntity::new);
+- register(Material.TRIAL_SPAWNER, CraftTrialSpawner.class, CraftTrialSpawner::new, TrialSpawnerBlockEntity::new);
+- register(Material.VAULT, CraftVault.class, CraftVault::new, VaultBlockEntity::new);
++ // Paper start - simplify
++ register(BlockEntityType.SIGN, CraftSign.class, CraftSign::new);
++ register(BlockEntityType.HANGING_SIGN, CraftHangingSign.class, CraftHangingSign::new);
++ register(BlockEntityType.SKULL, CraftSkull.class, CraftSkull::new);
++ register(BlockEntityType.COMMAND_BLOCK, CraftCommandBlock.class, CraftCommandBlock::new);
++ register(BlockEntityType.BANNER, CraftBanner.class, CraftBanner::new);
++ register(BlockEntityType.SHULKER_BOX, CraftShulkerBox.class, CraftShulkerBox::new);
++ register(BlockEntityType.BED, CraftBed.class, CraftBed::new);
++ register(BlockEntityType.BEEHIVE, CraftBeehive.class, CraftBeehive::new);
++ register(BlockEntityType.CAMPFIRE, CraftCampfire.class, CraftCampfire::new);
++ register(BlockEntityType.BARREL, CraftBarrel.class, CraftBarrel::new);
++ register(BlockEntityType.BEACON, CraftBeacon.class, CraftBeacon::new);
++ register(BlockEntityType.BELL, CraftBell.class, CraftBell::new);
++ register(BlockEntityType.BLAST_FURNACE, CraftBlastFurnace.class, CraftBlastFurnace::new);
++ register(BlockEntityType.BREWING_STAND, CraftBrewingStand.class, CraftBrewingStand::new);
++ register(BlockEntityType.CHEST, CraftChest.class, CraftChest::new);
++ register(BlockEntityType.CHISELED_BOOKSHELF, CraftChiseledBookshelf.class, CraftChiseledBookshelf::new);
++ register(BlockEntityType.COMPARATOR, CraftComparator.class, CraftComparator::new);
++ register(BlockEntityType.CONDUIT, CraftConduit.class, CraftConduit::new);
++ register(BlockEntityType.CREAKING_HEART, CraftCreakingHeart.class, CraftCreakingHeart::new);
++ register(BlockEntityType.DAYLIGHT_DETECTOR, CraftDaylightDetector.class, CraftDaylightDetector::new);
++ register(BlockEntityType.DECORATED_POT, CraftDecoratedPot.class, CraftDecoratedPot::new);
++ register(BlockEntityType.DISPENSER, CraftDispenser.class, CraftDispenser::new);
++ register(BlockEntityType.DROPPER, CraftDropper.class, CraftDropper::new);
++ register(BlockEntityType.ENCHANTING_TABLE, CraftEnchantingTable.class, CraftEnchantingTable::new);
++ register(BlockEntityType.ENDER_CHEST, CraftEnderChest.class, CraftEnderChest::new);
++ register(BlockEntityType.END_GATEWAY, CraftEndGateway.class, CraftEndGateway::new);
++ register(BlockEntityType.END_PORTAL, CraftEndPortal.class, CraftEndPortal::new);
++ register(BlockEntityType.FURNACE, CraftFurnaceFurnace.class, CraftFurnaceFurnace::new);
++ register(BlockEntityType.HOPPER, CraftHopper.class, CraftHopper::new);
++ register(BlockEntityType.JIGSAW, CraftJigsaw.class, CraftJigsaw::new);
++ register(BlockEntityType.JUKEBOX, CraftJukebox.class, CraftJukebox::new);
++ register(BlockEntityType.LECTERN, CraftLectern.class, CraftLectern::new);
++ register(BlockEntityType.PISTON, CraftMovingPiston.class, CraftMovingPiston::new);
++ register(BlockEntityType.SCULK_CATALYST, CraftSculkCatalyst.class, CraftSculkCatalyst::new);
++ register(BlockEntityType.SCULK_SENSOR, CraftSculkSensor.class, CraftSculkSensor::new);
++ register(BlockEntityType.SCULK_SHRIEKER, CraftSculkShrieker.class, CraftSculkShrieker::new);
++ register(BlockEntityType.CALIBRATED_SCULK_SENSOR, CraftCalibratedSculkSensor.class, CraftCalibratedSculkSensor::new);
++ register(BlockEntityType.SMOKER, CraftSmoker.class, CraftSmoker::new);
++ register(BlockEntityType.MOB_SPAWNER, CraftCreatureSpawner.class, CraftCreatureSpawner::new);
++ register(BlockEntityType.STRUCTURE_BLOCK, CraftStructureBlock.class, CraftStructureBlock::new);
++ register(BlockEntityType.BRUSHABLE_BLOCK, CraftBrushableBlock.class, CraftBrushableBlock::new); // note: spigot still uses CraftSuspiciousSand impl for that block type
++ register(BlockEntityType.TRAPPED_CHEST, CraftChest.class, CraftChest::new);
++ register(BlockEntityType.CRAFTER, CraftCrafter.class, CraftCrafter::new);
++ register(BlockEntityType.TRIAL_SPAWNER, CraftTrialSpawner.class, CraftTrialSpawner::new);
++ register(BlockEntityType.VAULT, CraftVault.class, CraftVault::new);
++ // Paper end
+ }
+
+ private static void register(Material blockType, BlockStateFactory<?> factory) {
+@@ -353,30 +187,33 @@ public final class CraftBlockStates {
+ }
+
+ private static <T extends BlockEntity, B extends CraftBlockEntityState<T>> void register(
+- Material blockType,
++ net.minecraft.world.level.block.entity.BlockEntityType<? extends T> blockEntityType, // Paper
+ Class<B> blockStateType,
+- BiFunction<World, T, B> blockStateConstructor,
+- BiFunction<BlockPos, net.minecraft.world.level.block.state.BlockState, T> tileEntityConstructor
++ BiFunction<World, T, B> blockStateConstructor // Paper
+ ) {
+- CraftBlockStates.register(Collections.singletonList(blockType), blockStateType, blockStateConstructor, tileEntityConstructor);
+- }
+-
+- private static <T extends BlockEntity, B extends CraftBlockEntityState<T>> void register(
+- List<Material> blockTypes,
+- Class<B> blockStateType,
+- BiFunction<World, T, B> blockStateConstructor,
+- BiFunction<BlockPos, net.minecraft.world.level.block.state.BlockState, T> tileEntityConstructor
+- ) {
+- BlockStateFactory<B> factory = new BlockEntityStateFactory<>(blockStateType, blockStateConstructor, tileEntityConstructor);
+- for (Material blockType : blockTypes) {
+- CraftBlockStates.register(blockType, factory);
++ // Paper start
++ BlockStateFactory<B> factory = new BlockEntityStateFactory<>(blockStateType, blockStateConstructor, blockEntityType); // Paper
++ for (net.minecraft.world.level.block.Block block : blockEntityType.validBlocks) {
++ CraftBlockStates.register(CraftBlockType.minecraftToBukkit(block), factory);
+ }
++ CraftBlockStates.register(blockEntityType, factory);
++ // Paper end
+ }
+
+ private static BlockStateFactory<?> getFactory(Material material) {
+ return CraftBlockStates.FACTORIES.getOrDefault(material, CraftBlockStates.DEFAULT_FACTORY);
+ }
+
++ // Paper start
++ private static BlockStateFactory<?> getFactory(Material material, BlockEntityType<?> type) {
++ if (type != null) {
++ return CraftBlockStates.FACTORIES_BY_BLOCK_ENTITY_TYPE.getOrDefault(type, getFactory(material));
++ } else {
++ return getFactory(material);
++ }
++ }
++ // Paper end
++
+ public static Class<? extends CraftBlockState> getBlockStateType(Material material) {
+ Preconditions.checkNotNull(material, "material is null");
+ return CraftBlockStates.getFactory(material).blockStateType;
+@@ -392,6 +229,13 @@ public final class CraftBlockStates {
+ return null;
+ }
+
++ // Paper start
++ public static Class<? extends CraftBlockState> getBlockStateType(BlockEntityType<?> blockEntityType) {
++ Preconditions.checkNotNull(blockEntityType, "blockEntityType is null");
++ return CraftBlockStates.getFactory(null, blockEntityType).blockStateType;
++ }
++ // Paper end
++
+ public static BlockState getBlockState(Block block) {
+ // Paper start
+ return CraftBlockStates.getBlockState(block, true);
+@@ -459,7 +303,7 @@ public final class CraftBlockStates {
+ if (world != null && tileEntity == null && CraftBlockStates.isTileEntityOptional(material)) {
+ factory = CraftBlockStates.DEFAULT_FACTORY;
+ } else {
+- factory = CraftBlockStates.getFactory(material);
++ factory = CraftBlockStates.getFactory(material, tileEntity != null ? tileEntity.getType() : null); // Paper
+ }
+ return factory.createBlockState(world, blockPosition, blockData, tileEntity);
+ }
+@@ -478,6 +322,14 @@ public final class CraftBlockStates {
+ return new CraftBlockState(CraftBlock.at(world, pos), flag);
+ }
+
++ // Paper start
++ @Nullable
++ public static BlockEntityType<?> getBlockEntityType(final Material material) {
++ final BlockStateFactory<?> factory = org.bukkit.craftbukkit.block.CraftBlockStates.FACTORIES.get(material);
++ return factory instanceof final BlockEntityStateFactory<?,?> blockEntityStateFactory ? blockEntityStateFactory.tileEntityConstructor : null;
++ }
++ // Paper end
++
+ private CraftBlockStates() {
+ }
+ }
+diff --git a/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java b/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java
+index c032daa6957df2ad8b621379e415ad925f5cc162..a9810c88e05ebc1af60c051faa45e50ee183924f 100644
+--- a/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java
++++ b/src/test/java/org/bukkit/craftbukkit/block/BlockStateTest.java
+@@ -7,6 +7,7 @@ import net.minecraft.core.registries.BuiltInRegistries;
+ import net.minecraft.world.level.block.Block;
+ import net.minecraft.world.level.block.EntityBlock;
+ import net.minecraft.world.level.block.entity.BlockEntity;
++import net.minecraft.world.level.block.entity.BlockEntityType;
+ import org.bukkit.Material;
+ import org.bukkit.support.environment.AllFeatures;
+ import org.junit.jupiter.api.Test;
+@@ -42,4 +43,13 @@ public class BlockStateTest {
+ }
+ }
+ }
++
++ // Paper start
++ @Test
++ public void testBlockEntityTypes() {
++ for (var blockEntityType : BuiltInRegistries.BLOCK_ENTITY_TYPE) {
++ org.junit.jupiter.api.Assertions.assertNotNull(CraftBlockStates.getBlockStateType(blockEntityType));
++ }
++ }
++ // Paper end
+ }
diff --git a/patches/server/0616-Configurable-feature-seeds.patch b/patches/server/0616-Configurable-feature-seeds.patch
new file mode 100644
index 0000000000..9a0502f3a7
--- /dev/null
+++ b/patches/server/0616-Configurable-feature-seeds.patch
@@ -0,0 +1,27 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Tue, 31 Aug 2021 17:05:27 +0200
+Subject: [PATCH] Configurable feature seeds
+
+Co-authored-by: Thonk <[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 416b1afcbab093f45900a4d55708609ba5a7de7a..fde17b4e2607fc443a33aea3a631aae6ccb71e2c 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
+@@ -437,7 +437,14 @@ public abstract class ChunkGenerator {
+ return (String) optional.orElseGet(placedfeature::toString);
+ };
+
+- seededrandom.setFeatureSeed(i, l1, l);
++ // Paper start - Configurable feature seeds; change populationSeed used in random
++ long featurePopulationSeed = i;
++ final long configFeatureSeed = generatoraccessseed.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedfeature.feature());
++ if (configFeatureSeed != -1) {
++ featurePopulationSeed = seededrandom.setDecorationSeed(configFeatureSeed, blockposition.getX(), blockposition.getZ()); // See seededrandom.setDecorationSeed from above
++ }
++ seededrandom.setFeatureSeed(featurePopulationSeed, l1, l);
++ // Paper end - Configurable feature seeds
+
+ try {
+ generatoraccessseed.setCurrentlyGenerating(supplier1);
diff --git a/patches/server/0617-Add-root-admin-user-detection.patch b/patches/server/0617-Add-root-admin-user-detection.patch
new file mode 100644
index 0000000000..df739cc33c
--- /dev/null
+++ b/patches/server/0617-Add-root-admin-user-detection.patch
@@ -0,0 +1,62 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: egg82 <[email protected]>
+Date: Sat, 11 Sep 2021 22:55:14 +0200
+Subject: [PATCH] Add root/admin user detection
+
+This patch detects whether or not the server is currently executing as a privileged user and spits out a warning.
+The warning serves as a sort-of PSA for newer server admins who don't understand the risks of running as root.
+We've seen plenty of bad/malicious plugins hit markets, and there's been a few close-calls with exploits in the past.
+Hopefully this helps mitigate some potential damage to servers, even if it is just a warning.
+
+Co-authored-by: Noah van der Aa <[email protected]>
+
+diff --git a/src/main/java/io/papermc/paper/util/ServerEnvironment.java b/src/main/java/io/papermc/paper/util/ServerEnvironment.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..68098dfe716e93aafcca4d8d5b5a81d8648b3654
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/util/ServerEnvironment.java
+@@ -0,0 +1,23 @@
++package io.papermc.paper.util;
++
++import com.sun.security.auth.module.NTSystem;
++import com.sun.security.auth.module.UnixSystem;
++import java.util.Set;
++import org.apache.commons.lang.SystemUtils;
++
++public class ServerEnvironment {
++ private static final boolean RUNNING_AS_ROOT_OR_ADMIN;
++ private static final String WINDOWS_HIGH_INTEGRITY_LEVEL = "S-1-16-12288";
++
++ static {
++ if (SystemUtils.IS_OS_WINDOWS) {
++ RUNNING_AS_ROOT_OR_ADMIN = Set.of(new NTSystem().getGroupIDs()).contains(WINDOWS_HIGH_INTEGRITY_LEVEL);
++ } else {
++ RUNNING_AS_ROOT_OR_ADMIN = new UnixSystem().getUid() == 0;
++ }
++ }
++
++ public static boolean userIsRootOrAdmin() {
++ return RUNNING_AS_ROOT_OR_ADMIN;
++ }
++}
+diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+index 21d6f728d6ecd35a05933e9406a386c36135a456..ac7fc2497b860a143ef08498f5f477a373a29be7 100644
+--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+@@ -193,6 +193,16 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
+ DedicatedServer.LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
+ }
+
++ // Paper start - detect running as root
++ if (io.papermc.paper.util.ServerEnvironment.userIsRootOrAdmin()) {
++ DedicatedServer.LOGGER.warn("****************************");
++ DedicatedServer.LOGGER.warn("YOU ARE RUNNING THIS SERVER AS AN ADMINISTRATIVE OR ROOT USER. THIS IS NOT ADVISED.");
++ DedicatedServer.LOGGER.warn("YOU ARE OPENING YOURSELF UP TO POTENTIAL RISKS WHEN DOING THIS.");
++ DedicatedServer.LOGGER.warn("FOR MORE INFORMATION, SEE https://madelinemiller.dev/blog/root-minecraft-server/");
++ DedicatedServer.LOGGER.warn("****************************");
++ }
++ // Paper end - detect running as root
++
+ DedicatedServer.LOGGER.info("Loading properties");
+ DedicatedServerProperties dedicatedserverproperties = this.settings.getProperties();
+
diff --git a/patches/server/0618-don-t-attempt-to-teleport-dead-entities.patch b/patches/server/0618-don-t-attempt-to-teleport-dead-entities.patch
new file mode 100644
index 0000000000..6dc9c02ca4
--- /dev/null
+++ b/patches/server/0618-don-t-attempt-to-teleport-dead-entities.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: sulu5890 <[email protected]>
+Date: Sun, 24 Oct 2021 22:48:14 -0500
+Subject: [PATCH] don't attempt to teleport dead entities
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 9d95be758a905209eb1de9ee9d3b0f5af5b14074..6e0b22b81d25db7a8f98bbf0fc6b4b69f4a678fd 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -718,7 +718,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ // CraftBukkit start
+ public void postTick() {
+ // No clean way to break out of ticking once the entity has been copied to a new world, so instead we move the portalling later in the tick cycle
+- if (!(this instanceof ServerPlayer)) {
++ if (!(this instanceof ServerPlayer) && this.isAlive()) { // Paper - don't attempt to teleport dead entities
+ this.handlePortal();
+ }
+ }
diff --git a/patches/server/0619-Prevent-excessive-velocity-through-repeated-crits.patch b/patches/server/0619-Prevent-excessive-velocity-through-repeated-crits.patch
new file mode 100644
index 0000000000..f0f62f6f2a
--- /dev/null
+++ b/patches/server/0619-Prevent-excessive-velocity-through-repeated-crits.patch
@@ -0,0 +1,41 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Thu, 25 Nov 2021 10:25:09 +0100
+Subject: [PATCH] Prevent excessive velocity through repeated crits
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index fb3cec13dd9e426904ce28a8755e6cc57296a42e..dfa1e3548f9b48c20f28712ecb06310fe9c856f5 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -2875,17 +2875,29 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ return this.hasEffect(MobEffects.JUMP) ? 0.1F * ((float) this.getEffect(MobEffects.JUMP).getAmplifier() + 1.0F) : 0.0F;
+ }
+
++ protected long lastJumpTime = 0L; // Paper - Prevent excessive velocity through repeated crits
+ @VisibleForTesting
+ public void jumpFromGround() {
+ float f = this.getJumpPower();
+
+ if (f > 1.0E-5F) {
+ Vec3 vec3d = this.getDeltaMovement();
++ // Paper start - Prevent excessive velocity through repeated crits
++ long time = System.nanoTime();
++ boolean canCrit = true;
++ if (this instanceof net.minecraft.world.entity.player.Player) {
++ canCrit = false;
++ if (time - this.lastJumpTime > (long)(0.250e9)) {
++ this.lastJumpTime = time;
++ canCrit = true;
++ }
++ }
++ // Paper end - Prevent excessive velocity through repeated crits
+
+ this.setDeltaMovement(vec3d.x, Math.max((double) f, vec3d.y), vec3d.z);
+ if (this.isSprinting()) {
+ float f1 = this.getYRot() * 0.017453292F;
+-
++ if (canCrit) // Paper - Prevent excessive velocity through repeated crits
+ this.addDeltaMovement(new Vec3((double) (-Mth.sin(f1)) * 0.2D, 0.0D, (double) Mth.cos(f1) * 0.2D));
+ }
+
diff --git a/patches/server/0620-Remove-client-side-code-using-deprecated-for-removal.patch b/patches/server/0620-Remove-client-side-code-using-deprecated-for-removal.patch
new file mode 100644
index 0000000000..4a04ac055a
--- /dev/null
+++ b/patches/server/0620-Remove-client-side-code-using-deprecated-for-removal.patch
@@ -0,0 +1,30 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Fri, 26 Nov 2021 15:09:58 -0800
+Subject: [PATCH] Remove client-side code using deprecated for removal
+ AccessController
+
+Fixes warnings on build
+
+diff --git a/src/main/java/net/minecraft/Util.java b/src/main/java/net/minecraft/Util.java
+index e78f40f7f10a2ed0675b3cb9a2c5730dbc0141db..cc5e2710d3edeaf60284ed95c33999c701d0678a 100644
+--- a/src/main/java/net/minecraft/Util.java
++++ b/src/main/java/net/minecraft/Util.java
+@@ -1087,16 +1087,7 @@ public class Util {
+ }
+
+ public void openUri(URI uri) {
+- try {
+- Process process = AccessController.doPrivileged(
+- (PrivilegedExceptionAction<Process>)(() -> Runtime.getRuntime().exec(this.getOpenUriArguments(uri)))
+- );
+- process.getInputStream().close();
+- process.getErrorStream().close();
+- process.getOutputStream().close();
+- } catch (IOException | PrivilegedActionException var3) {
+- Util.LOGGER.error("Couldn't open location '{}'", uri, var3);
+- }
++ throw new IllegalStateException("This method is not useful on dedicated servers."); // Paper - Fix warnings on build by removing client-only code
+ }
+
+ public void openFile(File file) {
diff --git a/patches/server/0621-Fix-Spigot-growth-modifiers.patch b/patches/server/0621-Fix-Spigot-growth-modifiers.patch
new file mode 100644
index 0000000000..4e7f192262
--- /dev/null
+++ b/patches/server/0621-Fix-Spigot-growth-modifiers.patch
@@ -0,0 +1,141 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Fri, 3 Dec 2021 17:09:24 -0800
+Subject: [PATCH] Fix Spigot growth modifiers
+
+Fixes kelp modifier changing growth for other crops
+Also add growth modifiers for glow berries, mangrove propagules,
+torchflower crops and pitcher plant crops
+Also fix above-mentioned modifiers from having the reverse effect
+
+Co-authored-by: Jake Potrebic <[email protected]>
+Co-authored-by: Noah van der Aa <[email protected]>
+Co-authored-by: Lulu13022002 <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java
+index b4618129397fa5dbec3a6438fc20c57d91d1d1dd..81e572783157926383dd9baa58d30f5419c1616f 100644
+--- a/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/CaveVinesBlock.java
+@@ -50,9 +50,18 @@ public class CaveVinesBlock extends GrowingPlantHeadBlock implements CaveVines {
+ return to.setValue(BERRIES, from.getValue(BERRIES));
+ }
+
++ // Paper start - Fix Spigot growth modifiers
++ @Override
++ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) {
++ final boolean value = random.nextFloat() < (level != null ? (0.11F * (level.spigotConfig.glowBerryModifier / 100.0F)) : 0.11F);
++ return (BlockState) super.getGrowIntoState(state, random).setValue(CaveVinesBlock.BERRIES, value);
++ }
++ // Paper end - Fix Spigot growth modifiers
++
+ @Override
+ protected BlockState getGrowIntoState(BlockState state, RandomSource random) {
+- return super.getGrowIntoState(state, random).setValue(BERRIES, Boolean.valueOf(random.nextFloat() < 0.11F));
++ // Paper start - Fix Spigot growth modifiers
++ return this.getGrowIntoState(state, random, null);
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/level/block/CropBlock.java b/src/main/java/net/minecraft/world/level/block/CropBlock.java
+index 6fe896079c0ae622976c2055f8d13cda5ed3ea3d..1967ff3fcb94988be85985c4754904f0077de066 100644
+--- a/src/main/java/net/minecraft/world/level/block/CropBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/CropBlock.java
+@@ -91,6 +91,10 @@ public class CropBlock extends BushBlock implements BonemealableBlock {
+ modifier = world.spigotConfig.carrotModifier;
+ } else if (this == Blocks.POTATOES) {
+ modifier = world.spigotConfig.potatoModifier;
++ // Paper start - Fix Spigot growth modifiers
++ } else if (this == Blocks.TORCHFLOWER_CROP) {
++ modifier = world.spigotConfig.torchFlowerModifier;
++ // Paper end - Fix Spigot growth modifiers
+ } else {
+ modifier = world.spigotConfig.wheatModifier;
+ }
+diff --git a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
+index c527f2d8f594d711d047a2a149efe37caaeb0308..9b424d7661fedf8ee1eb9f3167c62e563f04d4d1 100644
+--- a/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/GrowingPlantHeadBlock.java
+@@ -60,12 +60,18 @@ public abstract class GrowingPlantHeadBlock extends GrowingPlantBlock implements
+ BlockPos blockposition1 = pos.relative(this.growthDirection);
+
+ if (this.canGrowInto(world.getBlockState(blockposition1))) {
+- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random)); // CraftBukkit
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, blockposition1, this.getGrowIntoState(state, world.random, world)); // CraftBukkit // Paper - Fix Spigot growth modifiers
+ }
+ }
+
+ }
+
++ // Paper start - Fix Spigot growth modifiers
++ protected BlockState getGrowIntoState(BlockState state, RandomSource random, @javax.annotation.Nullable Level level) {
++ return this.getGrowIntoState(state, random);
++ }
++ // Paper end - Fix Spigot growth modifiers
++
+ protected BlockState getGrowIntoState(BlockState state, RandomSource random) {
+ return (BlockState) state.cycle(GrowingPlantHeadBlock.AGE);
+ }
+diff --git a/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java b/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java
+index 10e2b80b0b6010982258548b5084b9591177b558..6b18db68c8b95480992199126c6063ea728d2314 100644
+--- a/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/MangrovePropaguleBlock.java
+@@ -123,7 +123,7 @@ public class MangrovePropaguleBlock extends SaplingBlock implements SimpleWaterl
+ @Override
+ protected void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+ if (!isHanging(state)) {
+- if (random.nextInt(7) == 0) {
++ if (random.nextFloat() < (world.spigotConfig.saplingModifier / (100.0F * 7))) { // Paper - Fix Spigot growth modifiers
+ this.advanceTree(world, pos, state, random);
+ }
+ } else {
+diff --git a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java
+index 972d8833127090c01d620cab10b3eca3d3601710..ea6135edc76c06b7cd55930b620dfb05f939e4f6 100644
+--- a/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/PitcherCropBlock.java
+@@ -132,7 +132,7 @@ public class PitcherCropBlock extends DoublePlantBlock implements BonemealableBl
+ @Override
+ public void randomTick(BlockState state, ServerLevel world, BlockPos pos, RandomSource random) {
+ float f = CropBlock.getGrowthSpeed(this, world, pos);
+- boolean bl = random.nextInt((int)(25.0F / f) + 1) == 0;
++ boolean bl = random.nextFloat() < (world.spigotConfig.pitcherPlantModifier / (100.0F * (Math.floor(25.0F / f) + 1))); // Paper - Fix Spigot growth modifiers
+ if (bl) {
+ this.grow(world, state, pos, 1);
+ }
+diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java
+index 491dd55b2870093184a606efabd251c68cc24719..e76f96a5c48d1eda2f9bbb3e11dd79f23f9ab75c 100644
+--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java
++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java
+@@ -96,6 +96,7 @@ public class SpigotWorldConfig
+ public int beetrootModifier;
+ public int carrotModifier;
+ public int potatoModifier;
++ public int torchFlowerModifier; // Paper
+ public int wheatModifier;
+ public int wartModifier;
+ public int vineModifier;
+@@ -106,6 +107,8 @@ public class SpigotWorldConfig
+ public int twistingVinesModifier;
+ public int weepingVinesModifier;
+ public int caveVinesModifier;
++ public int glowBerryModifier; // Paper
++ public int pitcherPlantModifier; // Paper
+ private int getAndValidateGrowth(String crop)
+ {
+ int modifier = this.getInt( "growth." + crop.toLowerCase(java.util.Locale.ENGLISH) + "-modifier", 100 );
+@@ -129,6 +132,7 @@ public class SpigotWorldConfig
+ this.beetrootModifier = this.getAndValidateGrowth( "Beetroot" );
+ this.carrotModifier = this.getAndValidateGrowth( "Carrot" );
+ this.potatoModifier = this.getAndValidateGrowth( "Potato" );
++ this.torchFlowerModifier = this.getAndValidateGrowth("TorchFlower"); // Paper
+ this.wheatModifier = this.getAndValidateGrowth( "Wheat" );
+ this.wartModifier = this.getAndValidateGrowth( "NetherWart" );
+ this.vineModifier = this.getAndValidateGrowth( "Vine" );
+@@ -139,6 +143,8 @@ public class SpigotWorldConfig
+ this.twistingVinesModifier = this.getAndValidateGrowth( "TwistingVines" );
+ this.weepingVinesModifier = this.getAndValidateGrowth( "WeepingVines" );
+ this.caveVinesModifier = this.getAndValidateGrowth( "CaveVines" );
++ this.glowBerryModifier = this.getAndValidateGrowth("GlowBerry"); // Paper
++ this.pitcherPlantModifier = this.getAndValidateGrowth("PitcherPlant"); // Paper
+ }
+
+ public double itemMerge;
diff --git a/patches/server/0622-Prevent-ContainerOpenersCounter-openCount-from-going.patch b/patches/server/0622-Prevent-ContainerOpenersCounter-openCount-from-going.patch
new file mode 100644
index 0000000000..b017de75d1
--- /dev/null
+++ b/patches/server/0622-Prevent-ContainerOpenersCounter-openCount-from-going.patch
@@ -0,0 +1,18 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Shane Freeder <[email protected]>
+Date: Tue, 30 Nov 2021 05:30:35 +0000
+Subject: [PATCH] Prevent ContainerOpenersCounter openCount from going negative
+
+
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java b/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
+index 43e4e893f0e7eed1959e6ccfa43c39ff00f083b3..86dce6796f92a5b0ae2b1bd837267c4e3f6754d0 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/ContainerOpenersCounter.java
+@@ -69,6 +69,7 @@ public abstract class ContainerOpenersCounter {
+
+ public void decrementOpeners(Player player, Level world, BlockPos pos, BlockState state) {
+ int oldPower = Math.max(0, Math.min(15, this.openCount)); // CraftBukkit - Get power before new viewer is added
++ if (this.openCount == 0) return; // Paper - Prevent ContainerOpenersCounter openCount from going negative
+ int i = this.openCount--;
+
+ // CraftBukkit start - Call redstone event
diff --git a/patches/server/0623-Add-PlayerItemFrameChangeEvent.patch b/patches/server/0623-Add-PlayerItemFrameChangeEvent.patch
new file mode 100644
index 0000000000..b7e908ab6e
--- /dev/null
+++ b/patches/server/0623-Add-PlayerItemFrameChangeEvent.patch
@@ -0,0 +1,61 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: SamB440 <[email protected]>
+Date: Mon, 15 Nov 2021 18:10:10 +0000
+Subject: [PATCH] Add PlayerItemFrameChangeEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java
+index c0deabf9d86d086fbd8cbaac5a02badf5c01c870..30af4cbb17148c247a46c0346419d6c838dbc9d2 100644
+--- a/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java
++++ b/src/main/java/net/minecraft/world/entity/decoration/ItemFrame.java
+@@ -1,6 +1,7 @@
+ package net.minecraft.world.entity.decoration;
+
+ import javax.annotation.Nullable;
++import io.papermc.paper.event.player.PlayerItemFrameChangeEvent; // Paper - Add PlayerItemFrameChangeEvent
+ import net.minecraft.core.BlockPos;
+ import net.minecraft.core.Direction;
+ import net.minecraft.core.component.DataComponents;
+@@ -166,6 +167,13 @@ public class ItemFrame extends HangingEntity {
+ return true;
+ }
+ // CraftBukkit end
++ // Paper start - Add PlayerItemFrameChangeEvent
++ if (source.getEntity() instanceof Player player) {
++ var event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.REMOVE);
++ if (!event.callEvent()) return true; // return true here because you aren't cancelling the damage, just the change
++ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false);
++ }
++ // Paper end - Add PlayerItemFrameChangeEvent
+ this.dropItem(world, source.getEntity(), false);
+ this.gameEvent(GameEvent.BLOCK_CHANGE, source.getEntity());
+ this.playSound(this.getRemoveItemSound(), 1.0F, 1.0F);
+@@ -403,7 +411,13 @@ public class ItemFrame extends HangingEntity {
+ if (worldmap != null && worldmap.isTrackedCountOverLimit(256)) {
+ return InteractionResult.FAIL;
+ } else {
+- this.setItem(itemstack);
++ // Paper start - Add PlayerItemFrameChangeEvent
++ PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), itemstack.asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.PLACE);
++ if (!event.callEvent()) {
++ return InteractionResult.FAIL;
++ }
++ this.setItem(ItemStack.fromBukkitCopy(event.getItemStack()));
++ // Paper end - Add PlayerItemFrameChangeEvent
+ this.gameEvent(GameEvent.BLOCK_CHANGE, player);
+ itemstack.consume(1, player);
+ return InteractionResult.SUCCESS;
+@@ -412,6 +426,13 @@ public class ItemFrame extends HangingEntity {
+ return InteractionResult.PASS;
+ }
+ } else {
++ // Paper start - Add PlayerItemFrameChangeEvent
++ PlayerItemFrameChangeEvent event = new PlayerItemFrameChangeEvent((org.bukkit.entity.Player) player.getBukkitEntity(), (org.bukkit.entity.ItemFrame) this.getBukkitEntity(), this.getItem().asBukkitCopy(), PlayerItemFrameChangeEvent.ItemFrameChangeAction.ROTATE);
++ if (!event.callEvent()) {
++ return InteractionResult.FAIL;
++ }
++ setItem(ItemStack.fromBukkitCopy(event.getItemStack()), false, false);
++ // Paper end - Add PlayerItemFrameChangeEvent
+ this.playSound(this.getRotateItemSound(), 1.0F, 1.0F);
+ this.setRotation(this.getRotation() + 1);
+ this.gameEvent(GameEvent.BLOCK_CHANGE, player);
diff --git a/patches/server/0624-Optimize-HashMapPalette.patch b/patches/server/0624-Optimize-HashMapPalette.patch
new file mode 100644
index 0000000000..a490eb0c20
--- /dev/null
+++ b/patches/server/0624-Optimize-HashMapPalette.patch
@@ -0,0 +1,57 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: stonar96 <[email protected]>
+Date: Sun, 17 Jan 2021 01:11:36 +0100
+Subject: [PATCH] Optimize HashMapPalette
+
+HashMapPalette uses an instance of CrudeIncrementalIntIdentityHashBiMap
+internally. A Palette has a preset maximum size = 1 << bits.
+CrudeIncrementalIntIdentityHashBiMap has an initial size but is
+automatically resized. The CrudeIncrementalIntIdentityHashBiMap is created
+with the maximum size in the constructor of HashMapPalette, with the aim
+that it doesn't need to be resized anymore. However, there are two things
+that I think Mojang hasn't considered here:
+1) The CrudeIncrementalIntIdentityHashBiMap is resized, when its initial
+size is reached and not the next time, when a further object is added.
+2) HashMapPalette adds objects (unnecessarily) before checking if the
+initial size of CrudeIncrementalIntIdentityHashBiMap is reached.
+This means to actually avoid resize operations in
+CrudeIncrementalIntIdentityHashBiMap, one has to add 2 to the initial size
+or add 1 and check the size before adding objects. This commit implements
+the second approach. Note that this isn't only an optimization but also
+makes async reads of Palettes fail-safe. An async read while the
+CrudeIncrementalIntIdentityHashBiMap is resized is fatal and can even lead
+to corrupted data. This is also something that Anti-Xray is currently
+relying on.
+
+diff --git a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java
+index ac673cb38755852eef37e915f157f6a702117306..98dbeaf8bde15940e5b5d5d1f13fd4bb32f0a10d 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java
++++ b/src/main/java/net/minecraft/world/level/chunk/HashMapPalette.java
+@@ -20,7 +20,7 @@ public class HashMapPalette<T> implements Palette<T> {
+ }
+
+ public HashMapPalette(IdMap<T> idList, int indexBits, PaletteResize<T> listener) {
+- this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create(1 << indexBits));
++ this(idList, indexBits, listener, CrudeIncrementalIntIdentityHashBiMap.create((1 << indexBits) + 1)); // Paper - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap
+ }
+
+ private HashMapPalette(IdMap<T> idList, int indexBits, PaletteResize<T> listener, CrudeIncrementalIntIdentityHashBiMap<T> map) {
+@@ -38,10 +38,16 @@ public class HashMapPalette<T> implements Palette<T> {
+ public int idFor(T object) {
+ int i = this.values.getId(object);
+ if (i == -1) {
+- i = this.values.add(object);
+- if (i >= 1 << this.bits) {
++ // Paper start - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize
++ // We use size() instead of the result from add(K)
++ // This avoids adding another object unnecessarily
++ // Without this change, + 2 would be required in the constructor
++ if (this.values.size() >= 1 << this.bits) {
+ i = this.resizeHandler.onResize(this.bits + 1, object);
++ } else {
++ i = this.values.add(object);
+ }
++ // Paper end - Perf: Avoid unnecessary resize operation in CrudeIncrementalIntIdentityHashBiMap and optimize
+ }
+
+ return i;
diff --git a/patches/server/0625-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch b/patches/server/0625-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch
new file mode 100644
index 0000000000..cde811d346
--- /dev/null
+++ b/patches/server/0625-Fix-ChunkSnapshot-isSectionEmpty-int-and-optimize-Pa.patch
@@ -0,0 +1,44 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Thu, 9 Dec 2021 00:08:11 -0800
+Subject: [PATCH] Fix ChunkSnapshot#isSectionEmpty(int) and optimize
+ PalettedContainer copying by not using codecs
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+index be44b03527bd17344f5d835ba9d0b47e4b55d45f..08956b81b9a3e5caf3adce6699149491ff190d90 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+@@ -338,14 +338,17 @@ public class CraftChunk implements Chunk {
+ PalettedContainerRO<Holder<net.minecraft.world.level.biome.Biome>>[] biome = (includeBiome || includeBiomeTempRain) ? new PalettedContainer[cs.length] : null;
+
+ Registry<net.minecraft.world.level.biome.Biome> iregistry = this.worldServer.registryAccess().lookupOrThrow(Registries.BIOME);
+- Codec<PalettedContainerRO<Holder<net.minecraft.world.level.biome.Biome>>> biomeCodec = PalettedContainer.codecRO(iregistry.asHolderIdMap(), iregistry.holderByNameCodec(), PalettedContainer.Strategy.SECTION_BIOMES, iregistry.getOrThrow(Biomes.PLAINS));
+
+ for (int i = 0; i < cs.length; i++) {
+- CompoundTag data = new CompoundTag();
+
+- data.put("block_states", SerializableChunkData.BLOCK_STATE_CODEC.encodeStart(NbtOps.INSTANCE, cs[i].getStates()).getOrThrow());
+- sectionBlockIDs[i] = SerializableChunkData.BLOCK_STATE_CODEC.parse(NbtOps.INSTANCE, data.getCompound("block_states")).getOrThrow(SerializableChunkData.ChunkReadException::new);
+- sectionEmpty[i] = cs[i].hasOnlyAir();
++ // Paper start - Fix ChunkSnapshot#isSectionEmpty(int); and remove codec usage
++ sectionEmpty[i] = cs[i].hasOnlyAir(); // fix sectionEmpty array not being filled
++ if (!sectionEmpty[i]) {
++ sectionBlockIDs[i] = cs[i].getStates().copy(); // use copy instead of round tripping with codecs
++ } else {
++ sectionBlockIDs[i] = CraftChunk.emptyBlockIDs; // use cached instance for empty block sections
++ }
++ // Paper end - Fix ChunkSnapshot#isSectionEmpty(int)
+
+ LevelLightEngine lightengine = this.worldServer.getLightEngine();
+ DataLayer skyLightArray = lightengine.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(this.x, chunk.getSectionYFromSectionIndex(i), this.z)); // SPIGOT-7498: Convert section index
+@@ -364,8 +367,7 @@ public class CraftChunk implements Chunk {
+ }
+
+ if (biome != null) {
+- data.put("biomes", biomeCodec.encodeStart(NbtOps.INSTANCE, cs[i].getBiomes()).getOrThrow());
+- biome[i] = biomeCodec.parse(NbtOps.INSTANCE, data.getCompound("biomes")).getOrThrow(SerializableChunkData.ChunkReadException::new);
++ biome[i] = ((PalettedContainer<Holder<net.minecraft.world.level.biome.Biome>>) cs[i].getBiomes()).copy(); // Paper - Perf: use copy instead of round tripping with codecs
+ }
+ }
+
diff --git a/patches/server/0626-Add-more-Campfire-API.patch b/patches/server/0626-Add-more-Campfire-API.patch
new file mode 100644
index 0000000000..c490c151c3
--- /dev/null
+++ b/patches/server/0626-Add-more-Campfire-API.patch
@@ -0,0 +1,111 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: LemonCaramel <[email protected]>
+Date: Fri, 16 Jul 2021 00:39:03 +0900
+Subject: [PATCH] Add more Campfire API
+
+
+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 7fa1aea7942a1bc4d9779a9f8ab020ccd5566923..94072a9b65f69dfc3337907f8573081989467662 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
+@@ -46,12 +46,14 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable {
+ private final NonNullList<ItemStack> items;
+ public final int[] cookingProgress;
+ public final int[] cookingTime;
++ public final boolean[] stopCooking; // Paper - Add more Campfire API
+
+ public CampfireBlockEntity(BlockPos pos, BlockState state) {
+ super(BlockEntityType.CAMPFIRE, pos, state);
+ this.items = NonNullList.withSize(4, ItemStack.EMPTY);
+ this.cookingProgress = new int[4];
+ this.cookingTime = new int[4];
++ this.stopCooking = new boolean[4]; // Paper - Add more Campfire API
+ }
+
+ public static void cookTick(ServerLevel world, BlockPos pos, BlockState state, CampfireBlockEntity blockEntity, RecipeManager.CachedCheck<SingleRecipeInput, CampfireCookingRecipe> recipeMatchGetter) {
+@@ -62,7 +64,9 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable {
+
+ if (!itemstack.isEmpty()) {
+ flag = true;
++ if (!blockEntity.stopCooking[i]) { // Paper - Add more Campfire API
+ int j = blockEntity.cookingProgress[i]++;
++ } // Paper - Add more Campfire API
+
+ if (blockEntity.cookingProgress[i] >= blockEntity.cookingTime[i]) {
+ SingleRecipeInput singlerecipeinput = new SingleRecipeInput(itemstack);
+@@ -175,6 +179,16 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable {
+ System.arraycopy(aint, 0, this.cookingTime, 0, Math.min(this.cookingTime.length, aint.length));
+ }
+
++ // Paper start - Add more Campfire API
++ if (nbt.contains("Paper.StopCooking", org.bukkit.craftbukkit.util.CraftMagicNumbers.NBT.TAG_BYTE_ARRAY)) {
++ byte[] abyte = nbt.getByteArray("Paper.StopCooking");
++ boolean[] cookingState = new boolean[4];
++ for (int index = 0; index < abyte.length; index++) {
++ cookingState[index] = abyte[index] == 1;
++ }
++ System.arraycopy(cookingState, 0, this.stopCooking, 0, Math.min(this.stopCooking.length, abyte.length));
++ }
++ // Paper end - Add more Campfire API
+ }
+
+ @Override
+@@ -183,6 +197,13 @@ public class CampfireBlockEntity extends BlockEntity implements Clearable {
+ ContainerHelper.saveAllItems(nbt, this.items, true, registries);
+ nbt.putIntArray("CookingTimes", this.cookingProgress);
+ nbt.putIntArray("CookingTotalTimes", this.cookingTime);
++ // Paper start - Add more Campfire API
++ byte[] cookingState = new byte[4];
++ for (int index = 0; index < cookingState.length; index++) {
++ cookingState[index] = (byte) (this.stopCooking[index] ? 1 : 0);
++ }
++ nbt.putByteArray("Paper.StopCooking", cookingState);
++ // Paper end - Add more Campfire API
+ }
+
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java b/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java
+index 4850cbf2c326f1155e04a204abed2d200c02342d..a776bba2ec51c6aecce98a3abceb2c235522d99d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftCampfire.java
+@@ -62,4 +62,40 @@ public class CraftCampfire extends CraftBlockEntityState<CampfireBlockEntity> im
+ public CraftCampfire copy(Location location) {
+ return new CraftCampfire(this, location);
+ }
++
++ // Paper start
++ @Override
++ public void stopCooking() {
++ for (int i = 0; i < getSnapshot().stopCooking.length; ++i)
++ this.stopCooking(i);
++ }
++
++ @Override
++ public void startCooking() {
++ for (int i = 0; i < getSnapshot().stopCooking.length; ++i)
++ this.startCooking(i);
++ }
++
++ @Override
++ public boolean stopCooking(int index) {
++ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)");
++ boolean previous = this.isCookingDisabled(index);
++ getSnapshot().stopCooking[index] = true;
++ return previous;
++ }
++
++ @Override
++ public boolean startCooking(int index) {
++ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)");
++ boolean previous = this.isCookingDisabled(index);
++ getSnapshot().stopCooking[index] = false;
++ return previous;
++ }
++
++ @Override
++ public boolean isCookingDisabled(int index) {
++ org.apache.commons.lang.Validate.isTrue(-1 < index && index < 4, "Slot index must be between 0 (incl) to 3 (incl)");
++ return getSnapshot().stopCooking[index];
++ }
++ // Paper end
+ }
diff --git a/patches/server/0627-Forward-CraftEntity-in-teleport-command.patch b/patches/server/0627-Forward-CraftEntity-in-teleport-command.patch
new file mode 100644
index 0000000000..ea5a8eee4a
--- /dev/null
+++ b/patches/server/0627-Forward-CraftEntity-in-teleport-command.patch
@@ -0,0 +1,35 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 4 Dec 2021 17:04:47 -0800
+Subject: [PATCH] Forward CraftEntity in teleport command
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 6e0b22b81d25db7a8f98bbf0fc6b4b69f4a678fd..17c8a369e5a09276a3918dfb2bb004441e35a8e0 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -3511,6 +3511,13 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ }
+
+ public void restoreFrom(Entity original) {
++ // Paper start - Forward CraftEntity in teleport command
++ CraftEntity bukkitEntity = original.bukkitEntity;
++ if (bukkitEntity != null) {
++ bukkitEntity.setHandle(this);
++ this.bukkitEntity = bukkitEntity;
++ }
++ // Paper end - Forward CraftEntity in teleport command
+ CompoundTag nbttagcompound = original.saveWithoutId(new CompoundTag());
+
+ nbttagcompound.remove("Dimension");
+@@ -3648,8 +3655,8 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ entity.restoreFrom(this);
+ this.removeAfterChangingDimensions();
+ // CraftBukkit start - Forward the CraftEntity to the new entity
+- this.getBukkitEntity().setHandle(entity);
+- entity.bukkitEntity = this.getBukkitEntity();
++ //this.getBukkitEntity().setHandle(entity);
++ //entity.bukkitEntity = this.getBukkitEntity(); // Paper - forward CraftEntity in teleport command; moved to Entity#restoreFrom
+ // CraftBukkit end
+ entity.teleportSetPosition(PositionMoveRotation.of(teleportTarget), teleportTarget.relatives());
+ if (this.inWorld) world.addDuringTeleport(entity); // CraftBukkit - Don't spawn the new entity if the current entity isn't spawned
diff --git a/patches/server/0628-Improve-scoreboard-entries.patch b/patches/server/0628-Improve-scoreboard-entries.patch
new file mode 100644
index 0000000000..8acac86ce3
--- /dev/null
+++ b/patches/server/0628-Improve-scoreboard-entries.patch
@@ -0,0 +1,88 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 4 Nov 2021 12:31:24 -0700
+Subject: [PATCH] Improve scoreboard entries
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java
+index da1e4496d78a2c1b258ff8bb316414cb8a662ba2..b36e5574c10e6d70a399e2ac0704fd4f43dbb444 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java
++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftObjective.java
+@@ -144,6 +144,15 @@ final class CraftObjective extends CraftScoreboardComponent implements Objective
+ return new CraftScore(this, CraftScoreboard.getScoreHolder(entry));
+ }
+
++ // Paper start
++ @Override
++ public Score getScoreFor(org.bukkit.entity.Entity entity) throws IllegalArgumentException, IllegalStateException {
++ Preconditions.checkArgument(entity != null, "Entity cannot be null");
++ this.checkState();
++ return new CraftScore(this, ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle());
++ }
++ // Paper end
++
+ @Override
+ public void unregister() {
+ CraftScoreboard scoreboard = this.checkState();
+diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java
+index c650fc3712de01184509a03f1d1b388859e163d7..253574890a9ed23d38a84680ba1eb221dc72b310 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java
++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboard.java
+@@ -235,6 +235,26 @@ public final class CraftScoreboard implements org.bukkit.scoreboard.Scoreboard {
+ this.board.setDisplayObjective(CraftScoreboardTranslations.fromBukkitSlot(slot), null);
+ }
+
++ // Paper start
++ @Override
++ public ImmutableSet<Score> getScoresFor(org.bukkit.entity.Entity entity) throws IllegalArgumentException {
++ Preconditions.checkArgument(entity != null, "Entity cannot be null");
++ return this.getScores(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle());
++ }
++
++ @Override
++ public void resetScoresFor(org.bukkit.entity.Entity entity) throws IllegalArgumentException {
++ Preconditions.checkArgument(entity != null, "Entity cannot be null");
++ this.resetScores(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle());
++ }
++
++ @Override
++ public Team getEntityTeam(org.bukkit.entity.Entity entity) throws IllegalArgumentException {
++ Preconditions.checkArgument(entity != null, "Entity cannot be null");
++ return this.getEntryTeam(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName());
++ }
++ // Paper end
++
+ // CraftBukkit method
+ public Scoreboard getHandle() {
+ return this.board;
+diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java
+index 7900adb0b158bc17dd792dd082c338547bc1aa0a..27219bf2f16aed64c78623d44c3cc84aa9f47065 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java
++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java
+@@ -304,6 +304,26 @@ final class CraftTeam extends CraftScoreboardComponent implements Team {
+ }
+ }
+
++ // Paper start
++ @Override
++ public void addEntity(org.bukkit.entity.Entity entity) throws IllegalStateException, IllegalArgumentException {
++ Preconditions.checkArgument(entity != null, "Entity cannot be null");
++ this.addEntry(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName());
++ }
++
++ @Override
++ public boolean removeEntity(org.bukkit.entity.Entity entity) throws IllegalStateException, IllegalArgumentException {
++ Preconditions.checkArgument(entity != null, "Entity cannot be null");
++ return this.removeEntry(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName());
++ }
++
++ @Override
++ public boolean hasEntity(org.bukkit.entity.Entity entity) throws IllegalStateException, IllegalArgumentException {
++ Preconditions.checkArgument(entity != null, "Entity cannot be null");
++ return this.hasEntry(((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName());
++ }
++ // Paper end
++
+ public static Visibility bukkitToNotch(NameTagVisibility visibility) {
+ switch (visibility) {
+ case ALWAYS:
diff --git a/patches/server/0629-Entity-powdered-snow-API.patch b/patches/server/0629-Entity-powdered-snow-API.patch
new file mode 100644
index 0000000000..acfaef73e3
--- /dev/null
+++ b/patches/server/0629-Entity-powdered-snow-API.patch
@@ -0,0 +1,42 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 24 Oct 2021 20:58:43 -0700
+Subject: [PATCH] Entity powdered snow API
+
+== AT ==
+public net.minecraft.world.entity.monster.Skeleton inPowderSnowTime
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+index b13c947d2cfe085017b30cb0f8340dd64f338914..442b5f13e976dd63bf1dccc12eb8c3f16314c581 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+@@ -1095,6 +1095,13 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
+ }
+ // Paper end - raw entity serialization API
+
++ // Paper start - entity powdered snow API
++ @Override
++ public boolean isInPowderedSnow() {
++ return getHandle().isInPowderSnow || getHandle().wasInPowderSnow; // depending on the location in the entity "tick" either could be needed.
++ }
++ // Paper end - entity powdered snow API
++
+ // Paper start - missing entity api
+ @Override
+ public boolean isInvisible() { // Paper - moved up from LivingEntity
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java
+index a0ea54181de6c6685deef265cbe9f66aabbca42b..6f98da9be6aef35e3b5c940188b872459a383c8e 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftSkeleton.java
+@@ -45,4 +45,11 @@ public class CraftSkeleton extends CraftAbstractSkeleton implements Skeleton {
+ public SkeletonType getSkeletonType() {
+ return SkeletonType.NORMAL;
+ }
++
++ // Paper start
++ @Override
++ public int inPowderedSnowTime() {
++ return getHandle().inPowderSnowTime;
++ }
++ // Paper end
+ }
diff --git a/patches/server/0630-Add-API-for-item-entity-health.patch b/patches/server/0630-Add-API-for-item-entity-health.patch
new file mode 100644
index 0000000000..cd2757b544
--- /dev/null
+++ b/patches/server/0630-Add-API-for-item-entity-health.patch
@@ -0,0 +1,34 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 28 Aug 2021 09:00:45 -0700
+Subject: [PATCH] Add API for item entity health
+
+== AT ==
+public net.minecraft.world.entity.item.ItemEntity health
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
+index 4a15c3786edbfeae3367c0b20fb6aee11d62aea6..1a291dd8a287db30e71dcb315599fc4b038764c4 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java
+@@ -98,6 +98,21 @@ public class CraftItem extends CraftEntity implements Item {
+ public void setWillAge(boolean willAge) {
+ this.getHandle().age = willAge ? 0 : NO_AGE_TIME;
+ }
++
++ @Override
++ public int getHealth() {
++ return this.getHandle().health;
++ }
++
++ @Override
++ public void setHealth(int health) {
++ if (health <= 0) {
++ this.getHandle().getItem().onDestroyed(this.getHandle());
++ this.getHandle().discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PLUGIN);
++ } else {
++ this.getHandle().health = health;
++ }
++ }
+ // Paper end
+
+ @Override
diff --git a/patches/server/0631-Configurable-max-block-light-for-monster-spawning.patch b/patches/server/0631-Configurable-max-block-light-for-monster-spawning.patch
new file mode 100644
index 0000000000..8c19486a5f
--- /dev/null
+++ b/patches/server/0631-Configurable-max-block-light-for-monster-spawning.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Thu, 16 Dec 2021 09:40:39 +0100
+Subject: [PATCH] Configurable max block light for monster spawning
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Monster.java b/src/main/java/net/minecraft/world/entity/monster/Monster.java
+index c49853f25bdf1fbc7ec9700d421c6ddccabae05f..e2de074bbe7bab0e5a7aecc1fae4c5914a203dd4 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Monster.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Monster.java
+@@ -92,7 +92,7 @@ public abstract class Monster extends PathfinderMob implements Enemy {
+ return false;
+ } else {
+ DimensionType dimensionType = world.dimensionType();
+- int i = dimensionType.monsterSpawnBlockLightLimit();
++ int i = world.getLevel().paperConfig().entities.spawning.monsterSpawnMaxLightLevel.or(dimensionType.monsterSpawnBlockLightLimit()); // Paper - Configurable max block light for monster spawning
+ if (i < 15 && world.getBrightness(LightLayer.BLOCK, pos) > i) {
+ return false;
+ } else {
diff --git a/patches/server/0632-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch b/patches/server/0632-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch
new file mode 100644
index 0000000000..b239d74e25
--- /dev/null
+++ b/patches/server/0632-Fix-sticky-pistons-and-BlockPistonRetractEvent.patch
@@ -0,0 +1,85 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 22 Dec 2021 09:51:48 -0800
+Subject: [PATCH] Fix sticky pistons and BlockPistonRetractEvent
+
+There is an explicit check in the handling code for empty pistons that
+prevents sticky pistons from firing the event. However when we look back
+at the history we see that this check was originally added so that ONLY
+sticky pistons would fire the retract event. I'm not sure why.
+https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/commits/1092acbddf07edfa4100bc6824504ac75088e913
+
+Over the course of several updates, the meaning of that field appears to
+have changed from "is NOT sticky" to "is sticky". So now its having the
+opposite effect. Only normal pistons fire the retraction event. And like
+all things in CB, it's just been carried around since.
+
+If we are to believe the history, the correct fix for this issue is to
+flip it so it only fires for sticky pistons, but that puts us in a
+bind. It's already firing for non-sticky pistons, changing it now would
+likely result in breakage. Furthermore, there is little documentation as
+to WHY that was ever intended to be the case.
+
+Instead we opt to remove the check entirely so that the event fires for
+all piston types.
+
+Co-authored-by: Zach Brown <[email protected]>
+Co-authored-by: Madeline Miller <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
+index 4973d75b26880e39d42b5ef533896f43a1f07cba..e841fccb8f298ef692677583b468869f56dc722c 100644
+--- a/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/piston/PistonBaseBlock.java
+@@ -163,15 +163,15 @@ public class PistonBaseBlock extends DirectionalBlock {
+ }
+
+ // CraftBukkit start
+- if (!this.isSticky) {
+- org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
+- BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.<org.bukkit.block.Block>of(), CraftBlock.notchToBlockFace(enumdirection));
+- world.getCraftServer().getPluginManager().callEvent(event);
+-
+- if (event.isCancelled()) {
+- return;
+- }
+- }
++ // if (!this.isSticky) { // Paper - Fix sticky pistons and BlockPistonRetractEvent; Move further down
++ // org.bukkit.block.Block block = world.getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ());
++ // BlockPistonRetractEvent event = new BlockPistonRetractEvent(block, ImmutableList.<org.bukkit.block.Block>of(), CraftBlock.notchToBlockFace(enumdirection));
++ // world.getCraftServer().getPluginManager().callEvent(event);
++ //
++ // if (event.isCancelled()) {
++ // return;
++ // }
++ // }
+ // PAIL: checkME - what happened to setTypeAndData?
+ // CraftBukkit end
+ world.blockEvent(pos, this, b0, enumdirection.get3DDataValue());
+@@ -248,6 +248,13 @@ public class PistonBaseBlock extends DirectionalBlock {
+
+ BlockState iblockdata2 = (BlockState) ((BlockState) Blocks.MOVING_PISTON.defaultBlockState().setValue(MovingPistonBlock.FACING, enumdirection)).setValue(MovingPistonBlock.TYPE, this.isSticky ? PistonType.STICKY : PistonType.DEFAULT);
+
++ // Paper start - Fix sticky pistons and BlockPistonRetractEvent; Move empty piston retract call to fix multiple event fires
++ if (!this.isSticky) {
++ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) {
++ return false;
++ }
++ }
++ // Paper end - Fix sticky pistons and BlockPistonRetractEvent
+ world.setBlock(pos, iblockdata2, 20);
+ world.setBlockEntity(MovingPistonBlock.newMovingBlockEntity(pos, iblockdata2, (BlockState) this.defaultBlockState().setValue(PistonBaseBlock.FACING, Direction.from3DDataValue(data & 7)), enumdirection, false, true));
+ world.blockUpdated(pos, iblockdata2.getBlock());
+@@ -274,6 +281,13 @@ public class PistonBaseBlock extends DirectionalBlock {
+ if (type == 1 && !iblockdata3.isAir() && PistonBaseBlock.isPushable(iblockdata3, world, blockposition1, enumdirection.getOpposite(), false, enumdirection) && (iblockdata3.getPistonPushReaction() == PushReaction.NORMAL || iblockdata3.is(Blocks.PISTON) || iblockdata3.is(Blocks.STICKY_PISTON))) {
+ this.moveBlocks(world, pos, enumdirection, false);
+ } else {
++ // Paper start - Fix sticky pistons and BlockPistonRetractEvent; fire BlockPistonRetractEvent for sticky pistons retracting nothing (air)
++ if (type == TRIGGER_CONTRACT && iblockdata2.isAir()) {
++ if (!new BlockPistonRetractEvent(CraftBlock.at(world, pos), java.util.Collections.emptyList(), CraftBlock.notchToBlockFace(enumdirection)).callEvent()) {
++ return false;
++ }
++ }
++ // Paper end - Fix sticky pistons and BlockPistonRetractEvent
+ world.removeBlock(pos.relative(enumdirection), false);
+ }
+ }
diff --git a/patches/server/0633-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch b/patches/server/0633-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch
new file mode 100644
index 0000000000..cfaa295706
--- /dev/null
+++ b/patches/server/0633-Expose-isFuel-and-canSmelt-methods-to-FurnaceInvento.patch
@@ -0,0 +1,32 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: BillyGalbreath <[email protected]>
+Date: Thu, 23 Dec 2021 15:32:50 -0600
+Subject: [PATCH] Expose isFuel and canSmelt methods to FurnaceInventory
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java
+index 29a8cd7667860c4598a556e6ef3af39c731683db..33c970b467675429ad952925830ed334632fd3b6 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryFurnace.java
+@@ -40,6 +40,21 @@ public class CraftInventoryFurnace extends CraftInventory implements FurnaceInve
+ this.setItem(0, stack);
+ }
+
++ // Paper start
++ @Override
++ public boolean isFuel(ItemStack stack) {
++ net.minecraft.server.level.ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorlds().get(0)).getHandle();
++ return stack != null && !stack.getType().isEmpty() && world.fuelValues().isFuel(CraftItemStack.asNMSCopy(stack));
++ }
++
++ @Override
++ public boolean canSmelt(ItemStack stack) {
++ // data packs are always loaded in the main world
++ net.minecraft.server.level.ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld) org.bukkit.Bukkit.getWorlds().get(0)).getHandle();
++ return stack != null && !stack.getType().isEmpty() && world.recipeAccess().getRecipeFor(((AbstractFurnaceBlockEntity) this.inventory).recipeType, new net.minecraft.world.item.crafting.SingleRecipeInput(CraftItemStack.asNMSCopy(stack)), world).isPresent();
++ }
++ // Paper end
++
+ @Override
+ public Furnace getHolder() {
+ return (Furnace) this.inventory.getOwner();
diff --git a/patches/server/0634-Bucketable-API.patch b/patches/server/0634-Bucketable-API.patch
new file mode 100644
index 0000000000..274ccde059
--- /dev/null
+++ b/patches/server/0634-Bucketable-API.patch
@@ -0,0 +1,69 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Sun, 26 Dec 2021 14:03:17 -0500
+Subject: [PATCH] Bucketable API
+
+
+diff --git a/src/main/java/io/papermc/paper/entity/PaperBucketable.java b/src/main/java/io/papermc/paper/entity/PaperBucketable.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..d3fc2e5db9f3c20120b403bf03c3c340b9956cbd
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/entity/PaperBucketable.java
+@@ -0,0 +1,31 @@
++package io.papermc.paper.entity;
++
++import org.bukkit.Sound;
++import org.bukkit.craftbukkit.CraftSound;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.inventory.ItemStack;
++
++public interface PaperBucketable extends Bucketable {
++
++ net.minecraft.world.entity.animal.Bucketable getHandle();
++
++ @Override
++ default boolean isFromBucket() {
++ return this.getHandle().fromBucket();
++ }
++
++ @Override
++ default void setFromBucket(boolean fromBucket) {
++ this.getHandle().setFromBucket(fromBucket);
++ }
++
++ @Override
++ default ItemStack getBaseBucketItem() {
++ return CraftItemStack.asBukkitCopy(this.getHandle().getBucketItemStack());
++ }
++
++ @Override
++ default Sound getPickupSound() {
++ return CraftSound.minecraftToBukkit(this.getHandle().getPickupSound());
++ }
++}
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java
+index e730292edca4624400bdb89d555922c5f61db7a5..cbfca242f820d238b112f8ce64e9de8398c48a1c 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAxolotl.java
+@@ -4,7 +4,7 @@ import com.google.common.base.Preconditions;
+ import org.bukkit.craftbukkit.CraftServer;
+ import org.bukkit.entity.Axolotl;
+
+-public class CraftAxolotl extends CraftAnimals implements Axolotl {
++public class CraftAxolotl extends CraftAnimals implements Axolotl, io.papermc.paper.entity.PaperBucketable { // Paper - Bucketable API
+
+ public CraftAxolotl(CraftServer server, net.minecraft.world.entity.animal.axolotl.Axolotl entity) {
+ super(server, entity);
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java
+index da5150f4ca0397bf10053aab0c3ff18af5bc3f3c..eb10f94d5ed8ca89d3786138647dd43357609a6c 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFish.java
+@@ -4,7 +4,7 @@ import net.minecraft.world.entity.animal.AbstractFish;
+ import org.bukkit.craftbukkit.CraftServer;
+ import org.bukkit.entity.Fish;
+
+-public class CraftFish extends CraftWaterMob implements Fish {
++public class CraftFish extends CraftWaterMob implements Fish, io.papermc.paper.entity.PaperBucketable { // Paper - Bucketable API
+
+ public CraftFish(CraftServer server, AbstractFish entity) {
+ super(server, entity);
diff --git a/patches/server/0635-Validate-usernames.patch b/patches/server/0635-Validate-usernames.patch
new file mode 100644
index 0000000000..e8287bc641
--- /dev/null
+++ b/patches/server/0635-Validate-usernames.patch
@@ -0,0 +1,76 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Sat, 1 Jan 2022 05:19:37 -0800
+Subject: [PATCH] Validate usernames
+
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+index 1e4b288f20153ce0c91fabf164c5c8320c90ba7d..cb5dd77892283a1aaec45434fb99bb7f08ee5394 100644
+--- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+@@ -90,6 +90,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
+ private final String serverId;
+ private final boolean transferred;
+ private ServerPlayer player; // CraftBukkit
++ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding
+
+ public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) {
+ this.state = ServerLoginPacketListenerImpl.State.HELLO;
+@@ -171,7 +172,13 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
+ @Override
+ public void handleHello(ServerboundHelloPacket packet) {
+ Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]);
+- Validate.validState(StringUtil.isValidPlayerName(packet.name()), "Invalid characters in username", new Object[0]);
++ // Paper start - Validate usernames
++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode()
++ && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation
++ && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) {
++ Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username", new Object[0]);
++ }
++ // Paper end - Validate usernames
+ this.requestedUsername = packet.name();
+ GameProfile gameprofile = this.server.getSingleplayerProfile();
+
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 05d2f3c26d10169f6cf43bcb6c48db5d27b5cbac..3a0e0196f5bfa554b23fff9ff1a18a189b36452e 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -627,7 +627,7 @@ public abstract class PlayerList {
+
+ for (int i = 0; i < this.players.size(); ++i) {
+ entityplayer = (ServerPlayer) this.players.get(i);
+- if (entityplayer.getUUID().equals(uuid)) {
++ if (entityplayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && entityplayer.getGameProfile().getName().equalsIgnoreCase(gameprofile.getName()))) { // Paper - validate usernames
+ list.add(entityplayer);
+ }
+ }
+diff --git a/src/main/java/net/minecraft/util/StringUtil.java b/src/main/java/net/minecraft/util/StringUtil.java
+index e588bd7ef0616dc88ce4c0feeeabadc29dcaa550..6c33002dc8bbb3759c3156302ab7d1f26ce5e8ee 100644
+--- a/src/main/java/net/minecraft/util/StringUtil.java
++++ b/src/main/java/net/minecraft/util/StringUtil.java
+@@ -67,6 +67,25 @@ public class StringUtil {
+ return name.length() <= 16 && name.chars().filter(c -> c <= 32 || c >= 127).findAny().isEmpty();
+ }
+
++ // Paper start - Username validation
++ public static boolean isReasonablePlayerName(final String name) {
++ if (name.isEmpty() || name.length() > 16) {
++ return false;
++ }
++
++ for (int i = 0, len = name.length(); i < len; ++i) {
++ final char c = name.charAt(i);
++ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_' || c == '.')) {
++ continue;
++ }
++
++ return false;
++ }
++
++ return true;
++ }
++ // Paper end - Username validation
++
+ public static String filterText(String string) {
+ return filterText(string, false);
+ }
diff --git a/patches/server/0636-Make-water-animal-spawn-height-configurable.patch b/patches/server/0636-Make-water-animal-spawn-height-configurable.patch
new file mode 100644
index 0000000000..341b55b1f6
--- /dev/null
+++ b/patches/server/0636-Make-water-animal-spawn-height-configurable.patch
@@ -0,0 +1,21 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Brokkonaut <[email protected]>
+Date: Sat, 18 Dec 2021 08:26:55 +0100
+Subject: [PATCH] Make water animal spawn height configurable
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java
+index 6a8a90059dcc524a0724264c8d604bf39228a650..8c4532a250f8679d729a35c17e9b5bd339264450 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java
++++ b/src/main/java/net/minecraft/world/entity/animal/WaterAnimal.java
+@@ -70,6 +70,10 @@ public abstract class WaterAnimal extends PathfinderMob {
+ ) {
+ int i = world.getSeaLevel();
+ int j = i - 13;
++ // Paper start - Make water animal spawn height configurable
++ i = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.maximum.or(i);
++ j = world.getMinecraftWorld().paperConfig().entities.spawning.wateranimalSpawnHeight.minimum.or(j);
++ // Paper end - Make water animal spawn height configurable
+ return pos.getY() >= j && pos.getY() <= i && world.getFluidState(pos.below()).is(FluidTags.WATER) && world.getBlockState(pos.above()).is(Blocks.WATER);
+ }
+ }
diff --git a/patches/server/0637-Expose-vanilla-BiomeProvider-from-WorldInfo.patch b/patches/server/0637-Expose-vanilla-BiomeProvider-from-WorldInfo.patch
new file mode 100644
index 0000000000..af63e382de
--- /dev/null
+++ b/patches/server/0637-Expose-vanilla-BiomeProvider-from-WorldInfo.patch
@@ -0,0 +1,177 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Thu, 6 Jan 2022 15:59:06 -0800
+Subject: [PATCH] Expose vanilla BiomeProvider from WorldInfo
+
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index edd2e83df282b0e24d4c7e3a34776a5b039c2c6b..c133a646baf88e0489d358e302d67f21f76b47c3 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -625,7 +625,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(iworlddataserver));
+ LevelStem worlddimension = (LevelStem) dimensions.getValue(dimensionKey);
+
+- org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value());
++ org.bukkit.generator.WorldInfo worldInfo = new org.bukkit.craftbukkit.generator.CraftWorldInfo(iworlddataserver, worldSession, org.bukkit.World.Environment.getEnvironment(dimension), worlddimension.type().value(), worlddimension.generator(), this.registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo
+ if (biomeProvider == null && gen != null) {
+ biomeProvider = gen.getDefaultBiomeProvider(worldInfo);
+ }
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index 3205ef2b0027a2fa7f9ba5ed3437f71f1c6e02b5..fda4b5f2b848b432138207eff9a77fed6aaf3805 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -363,7 +363,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ this.serverLevelData.setWorld(this);
+
+ if (biomeProvider != null) {
+- BiomeSource worldChunkManager = new CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME));
++ BiomeSource worldChunkManager = new CustomWorldChunkManager(this.getWorld(), biomeProvider, this.server.registryAccess().lookupOrThrow(Registries.BIOME), chunkgenerator.getBiomeSource()); // Paper - add vanillaBiomeProvider
+ if (chunkgenerator instanceof NoiseBasedChunkGenerator cga) {
+ chunkgenerator = new NoiseBasedChunkGenerator(worldChunkManager, cga.settings);
+ } else if (chunkgenerator instanceof FlatLevelSource cpf) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 33d9f3778996eedc83064332a2fbbdc7c6a8ba90..62ab88e022230d25ffb359981ce7da4e64a9be5a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -1310,7 +1310,7 @@ public final class CraftServer implements Server {
+ List<CustomSpawner> list = ImmutableList.of(new PhantomSpawner(), new PatrolSpawner(), new CatSpawner(), new VillageSiege(), new WanderingTraderSpawner(worlddata));
+ LevelStem worlddimension = iregistry.getValue(actualDimension);
+
+- WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value());
++ WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), worlddimension.type().value(), worlddimension.generator(), this.getHandle().getServer().registryAccess()); // Paper - Expose vanilla BiomeProvider from WorldInfo
+ if (biomeProvider == null && generator != null) {
+ biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index 744e3631cd2d5c157c9b6023ca813e57c6f860d6..66778ebd82563823f692c7151f40a373e8d7427a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -209,6 +209,39 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+ public int getPlayerCount() {
+ return world.players().size();
+ }
++
++ @Override
++ public BiomeProvider vanillaBiomeProvider() {
++ net.minecraft.server.level.ServerChunkCache serverCache = this.getHandle().chunkSource;
++
++ final net.minecraft.world.level.chunk.ChunkGenerator gen = serverCache.getGenerator();
++ net.minecraft.world.level.biome.BiomeSource biomeSource;
++ if (gen instanceof org.bukkit.craftbukkit.generator.CustomChunkGenerator custom) {
++ biomeSource = custom.getDelegate().getBiomeSource();
++ } else {
++ biomeSource = gen.getBiomeSource();
++ }
++ if (biomeSource instanceof org.bukkit.craftbukkit.generator.CustomWorldChunkManager customBiomeSource) {
++ biomeSource = customBiomeSource.vanillaBiomeSource;
++ }
++ final net.minecraft.world.level.biome.BiomeSource finalBiomeSource = biomeSource;
++ final net.minecraft.world.level.biome.Climate.Sampler sampler = serverCache.randomState().sampler();
++
++ final List<Biome> possibleBiomes = finalBiomeSource.possibleBiomes().stream()
++ .map(CraftBiome::minecraftHolderToBukkit)
++ .toList();
++ return new BiomeProvider() {
++ @Override
++ public Biome getBiome(final org.bukkit.generator.WorldInfo worldInfo, final int x, final int y, final int z) {
++ return CraftBiome.minecraftHolderToBukkit(finalBiomeSource.getNoiseBiome(x >> 2, y >> 2, z >> 2, sampler));
++ }
++
++ @Override
++ public List<Biome> getBiomes(final org.bukkit.generator.WorldInfo worldInfo) {
++ return possibleBiomes;
++ }
++ };
++ }
+ // Paper end
+
+ private static final Random rand = new Random();
+diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java
+index 5d655d6cd3e23e0287069f8bdf77601487e862fd..c81455a4ee9a3185f125ebf8cec325f4ed2e501d 100644
+--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java
++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftWorldInfo.java
+@@ -17,8 +17,14 @@ public class CraftWorldInfo implements WorldInfo {
+ private final long seed;
+ private final int minHeight;
+ private final int maxHeight;
++ // Paper start
++ private final net.minecraft.world.level.chunk.ChunkGenerator vanillaChunkGenerator;
++ private final net.minecraft.core.RegistryAccess.Frozen registryAccess;
+
+- public CraftWorldInfo(ServerLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager) {
++ public CraftWorldInfo(PrimaryLevelData worldDataServer, LevelStorageSource.LevelStorageAccess session, World.Environment environment, DimensionType dimensionManager, net.minecraft.world.level.chunk.ChunkGenerator chunkGenerator, net.minecraft.core.RegistryAccess.Frozen registryAccess) {
++ this.registryAccess = registryAccess;
++ this.vanillaChunkGenerator = chunkGenerator;
++ // Paper end
+ this.name = worldDataServer.getLevelName();
+ this.uuid = WorldUUID.getUUID(session.levelDirectory.path().toFile());
+ this.environment = environment;
+@@ -27,15 +33,6 @@ public class CraftWorldInfo implements WorldInfo {
+ this.maxHeight = dimensionManager.minY() + dimensionManager.height();
+ }
+
+- public CraftWorldInfo(String name, UUID uuid, World.Environment environment, long seed, int minHeight, int maxHeight) {
+- this.name = name;
+- this.uuid = uuid;
+- this.environment = environment;
+- this.seed = seed;
+- this.minHeight = minHeight;
+- this.maxHeight = maxHeight;
+- }
+-
+ @Override
+ public String getName() {
+ return this.name;
+@@ -65,4 +62,34 @@ public class CraftWorldInfo implements WorldInfo {
+ public int getMaxHeight() {
+ return this.maxHeight;
+ }
++
++ // Paper start
++ @Override
++ public org.bukkit.generator.BiomeProvider vanillaBiomeProvider() {
++ final net.minecraft.world.level.levelgen.RandomState randomState;
++ if (vanillaChunkGenerator instanceof net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator noiseBasedChunkGenerator) {
++ randomState = net.minecraft.world.level.levelgen.RandomState.create(noiseBasedChunkGenerator.generatorSettings().value(),
++ registryAccess.lookupOrThrow(net.minecraft.core.registries.Registries.NOISE), getSeed());
++ } else {
++ randomState = net.minecraft.world.level.levelgen.RandomState.create(net.minecraft.world.level.levelgen.NoiseGeneratorSettings.dummy(),
++ registryAccess.lookupOrThrow(net.minecraft.core.registries.Registries.NOISE), getSeed());
++ }
++
++ final java.util.List<org.bukkit.block.Biome> possibleBiomes = CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().possibleBiomes().stream()
++ .map(biome -> org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit(biome))
++ .toList();
++ return new org.bukkit.generator.BiomeProvider() {
++ @Override
++ public org.bukkit.block.Biome getBiome(final WorldInfo worldInfo, final int x, final int y, final int z) {
++ return org.bukkit.craftbukkit.block.CraftBiome.minecraftHolderToBukkit(
++ CraftWorldInfo.this.vanillaChunkGenerator.getBiomeSource().getNoiseBiome(x >> 2, y >> 2, z >> 2, randomState.sampler()));
++ }
++
++ @Override
++ public java.util.List<org.bukkit.block.Biome> getBiomes(final org.bukkit.generator.WorldInfo worldInfo) {
++ return possibleBiomes;
++ }
++ };
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java
+index 0063c4c17d05a77adf81164fb9307a29860cbe12..0bac128d6faff0063b03f595b82deea78d1ae161 100644
+--- a/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java
++++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomWorldChunkManager.java
+@@ -31,7 +31,11 @@ public class CustomWorldChunkManager extends BiomeSource {
+ return biomeBases;
+ }
+
+- public CustomWorldChunkManager(WorldInfo worldInfo, BiomeProvider biomeProvider, Registry<net.minecraft.world.level.biome.Biome> registry) {
++ // Paper start - add vanillaBiomeProvider
++ public final BiomeSource vanillaBiomeSource;
++ public CustomWorldChunkManager(WorldInfo worldInfo, BiomeProvider biomeProvider, Registry<net.minecraft.world.level.biome.Biome> registry, BiomeSource vanillaBiomeSource) {
++ this.vanillaBiomeSource = vanillaBiomeSource;
++ // Paper end - add vanillaBiomeProvider
+ this.worldInfo = worldInfo;
+ this.biomeProvider = biomeProvider;
+ this.registry = registry;
diff --git a/patches/server/0638-Add-config-option-for-worlds-affected-by-time-cmd.patch b/patches/server/0638-Add-config-option-for-worlds-affected-by-time-cmd.patch
new file mode 100644
index 0000000000..2146865230
--- /dev/null
+++ b/patches/server/0638-Add-config-option-for-worlds-affected-by-time-cmd.patch
@@ -0,0 +1,28 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 2 Jan 2022 22:34:51 -0800
+Subject: [PATCH] Add config option for worlds affected by time cmd
+
+
+diff --git a/src/main/java/net/minecraft/server/commands/TimeCommand.java b/src/main/java/net/minecraft/server/commands/TimeCommand.java
+index ec4ea21f14f8dbf6a26b6124256386ff51c0628b..8b83d747de831878ff45dc74b4ae7cd9efb21d8c 100644
+--- a/src/main/java/net/minecraft/server/commands/TimeCommand.java
++++ b/src/main/java/net/minecraft/server/commands/TimeCommand.java
+@@ -53,7 +53,7 @@ public class TimeCommand {
+ }
+
+ public static int setTime(CommandSourceStack source, int time) {
+- Iterator iterator = com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in
++ Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
+
+ while (iterator.hasNext()) {
+ ServerLevel worldserver = (ServerLevel) iterator.next();
+@@ -75,7 +75,7 @@ public class TimeCommand {
+ }
+
+ public static int addTime(CommandSourceStack source, int time) {
+- Iterator iterator = com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in
++ Iterator iterator = io.papermc.paper.configuration.GlobalConfiguration.get().commands.timeCommandAffectsAllWorlds ? source.getServer().getAllLevels().iterator() : com.google.common.collect.Iterators.singletonIterator(source.getLevel()); // CraftBukkit - SPIGOT-6496: Only set the time for the world the command originates in // Paper - add config option for spigot's change
+
+ while (iterator.hasNext()) {
+ ServerLevel worldserver = (ServerLevel) iterator.next();
diff --git a/patches/server/0639-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch b/patches/server/0639-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch
new file mode 100644
index 0000000000..be0eedab93
--- /dev/null
+++ b/patches/server/0639-Add-missing-IAE-check-for-PersistentDataContainer-ha.patch
@@ -0,0 +1,18 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: u9g <[email protected]>
+Date: Mon, 3 Jan 2022 23:32:42 -0500
+Subject: [PATCH] Add missing IAE check for PersistentDataContainer#has
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java
+index 3001bb0e3d4af9b16645a0136093db594b89ab01..984e988a47aa55a3fd92198e379d0f92f511daef 100644
+--- a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java
++++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataContainer.java
+@@ -57,6 +57,7 @@ public class CraftPersistentDataContainer implements PersistentDataContainer {
+
+ @Override
+ public boolean has(NamespacedKey key) {
++ Preconditions.checkArgument(key != null, "The provided key for the custom value was null"); // Paper
+ return this.customDataTags.get(key.toString()) != null;
+ }
+
diff --git a/patches/server/0640-Multiple-Entries-with-Scoreboards.patch b/patches/server/0640-Multiple-Entries-with-Scoreboards.patch
new file mode 100644
index 0000000000..558cc93da5
--- /dev/null
+++ b/patches/server/0640-Multiple-Entries-with-Scoreboards.patch
@@ -0,0 +1,125 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Cryptite <[email protected]>
+Date: Tue, 21 Sep 2021 18:17:33 -0500
+Subject: [PATCH] Multiple Entries with Scoreboards
+
+
+diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java
+index 1d0c473442b5c72245c356054440323e3c5d4711..f8fe125f12a6a00899d1d6acfa448be882b81557 100644
+--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java
++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket.java
+@@ -58,6 +58,11 @@ public class ClientboundSetPlayerTeamPacket implements Packet<ClientGamePacketLi
+ );
+ }
+
++ // Paper start - Multiple Entries with Scoreboards
++ public static ClientboundSetPlayerTeamPacket createMultiplePlayerPacket(PlayerTeam team, Collection<String> players, ClientboundSetPlayerTeamPacket.Action operation) {
++ return new ClientboundSetPlayerTeamPacket(team.getName(), operation == ClientboundSetPlayerTeamPacket.Action.ADD ? 3 : 4, Optional.empty(), players);
++ }
++ // Paper end - Multiple Entries with Scoreboards
+ private ClientboundSetPlayerTeamPacket(RegistryFriendlyByteBuf buf) {
+ this.name = buf.readUtf();
+ this.method = buf.readByte();
+diff --git a/src/main/java/net/minecraft/server/ServerScoreboard.java b/src/main/java/net/minecraft/server/ServerScoreboard.java
+index f1e72463ca11658390f662efbaf3e551c05fe799..8fd4d8ffcc9e1a0fcf83730d26c3bb9bef0f73f2 100644
+--- a/src/main/java/net/minecraft/server/ServerScoreboard.java
++++ b/src/main/java/net/minecraft/server/ServerScoreboard.java
+@@ -106,6 +106,25 @@ public class ServerScoreboard extends Scoreboard {
+ }
+ }
+
++ // Paper start - Multiple Entries with Scoreboards
++ public boolean addPlayersToTeam(java.util.Collection<String> players, PlayerTeam team) {
++ boolean anyAdded = false;
++ for (String playerName : players) {
++ if (super.addPlayerToTeam(playerName, team)) {
++ anyAdded = true;
++ }
++ }
++
++ if (anyAdded) {
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.ADD));
++ this.setDirty();
++ return true;
++ } else {
++ return false;
++ }
++ }
++ // Paper end - Multiple Entries with Scoreboards
++
+ @Override
+ public void removePlayerFromTeam(String scoreHolderName, PlayerTeam team) {
+ super.removePlayerFromTeam(scoreHolderName, team);
+@@ -113,6 +132,17 @@ public class ServerScoreboard extends Scoreboard {
+ this.setDirty();
+ }
+
++ // Paper start - Multiple Entries with Scoreboards
++ public void removePlayersFromTeam(java.util.Collection<String> players, PlayerTeam team) {
++ for (String playerName : players) {
++ super.removePlayerFromTeam(playerName, team);
++ }
++
++ this.broadcastAll(ClientboundSetPlayerTeamPacket.createMultiplePlayerPacket(team, players, ClientboundSetPlayerTeamPacket.Action.REMOVE));
++ this.setDirty();
++ }
++ // Paper end - Multiple Entries with Scoreboards
++
+ @Override
+ public void onObjectiveAdded(Objective objective) {
+ super.onObjectiveAdded(objective);
+diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java
+index 27219bf2f16aed64c78623d44c3cc84aa9f47065..2b335c750ce5f9ccc2651a8701497ca9b8f46704 100644
+--- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java
++++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftTeam.java
+@@ -229,6 +229,21 @@ final class CraftTeam extends CraftScoreboardComponent implements Team {
+ scoreboard.board.addPlayerToTeam(entry, this.team);
+ }
+
++ // Paper start - Multiple Entries with Scoreboards
++ @Override
++ public void addEntities(java.util.Collection<org.bukkit.entity.Entity> entities) throws IllegalStateException, IllegalArgumentException {
++ this.addEntries(entities.stream().map(entity -> ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()).toList());
++ }
++
++ @Override
++ public void addEntries(java.util.Collection<String> entries) throws IllegalStateException, IllegalArgumentException {
++ Preconditions.checkArgument(entries != null, "Entries cannot be null");
++ CraftScoreboard scoreboard = this.checkState();
++
++ ((net.minecraft.server.ServerScoreboard) scoreboard.board).addPlayersToTeam(entries, this.team);
++ }
++ // Paper end - Multiple Entries with Scoreboards
++
+ @Override
+ public boolean removePlayer(OfflinePlayer player) {
+ Preconditions.checkArgument(player != null, "OfflinePlayer cannot be null");
+@@ -248,6 +263,28 @@ final class CraftTeam extends CraftScoreboardComponent implements Team {
+ return true;
+ }
+
++ // Paper start - Multiple Entries with Scoreboards
++ @Override
++ public boolean removeEntities(java.util.Collection<org.bukkit.entity.Entity> entities) throws IllegalStateException, IllegalArgumentException {
++ return this.removeEntries(entities.stream().map(entity -> ((org.bukkit.craftbukkit.entity.CraftEntity) entity).getHandle().getScoreboardName()).toList());
++ }
++
++ @Override
++ public boolean removeEntries(java.util.Collection<String> entries) throws IllegalStateException, IllegalArgumentException {
++ Preconditions.checkArgument(entries != null, "Entry cannot be null");
++ CraftScoreboard scoreboard = this.checkState();
++
++ for (String entry : entries) {
++ if (this.team.getPlayers().contains(entry)) {
++ ((net.minecraft.server.ServerScoreboard) scoreboard.board).removePlayersFromTeam(entries, this.team);
++ return true;
++ }
++ }
++
++ return false;
++ }
++ // Paper end - Multiple Entries with Scoreboards
++
+ @Override
+ public boolean hasPlayer(OfflinePlayer player) throws IllegalArgumentException, IllegalStateException {
+ Preconditions.checkArgument(player != null, "OfflinePlayer cannot be null");
diff --git a/patches/server/0641-Reset-placed-block-on-exception.patch b/patches/server/0641-Reset-placed-block-on-exception.patch
new file mode 100644
index 0000000000..09609b372b
--- /dev/null
+++ b/patches/server/0641-Reset-placed-block-on-exception.patch
@@ -0,0 +1,39 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Fri, 7 Jan 2022 11:45:15 +0100
+Subject: [PATCH] Reset placed block on exception
+
+
+diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java
+index 2527fea68885c6911d01cd5a9b08a347d30844c8..cdd41eb371b1dd244a72c863cc5c8c4e84a9a71a 100644
+--- a/src/main/java/net/minecraft/world/item/BlockItem.java
++++ b/src/main/java/net/minecraft/world/item/BlockItem.java
+@@ -71,6 +71,7 @@ public class BlockItem extends Item {
+ if (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem) {
+ blockstate = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos());
+ }
++ final org.bukkit.block.BlockState oldBlockstate = blockstate != null ? blockstate : org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); // Paper - Reset placed block on exception
+ // CraftBukkit end
+
+ if (iblockdata == null) {
+@@ -86,8 +87,20 @@ public class BlockItem extends Item {
+
+ if (iblockdata1.is(iblockdata.getBlock())) {
+ iblockdata1 = this.updateBlockStateFromTag(blockposition, world, itemstack, iblockdata1);
++ // Paper start - Reset placed block on exception
++ try {
+ this.updateCustomBlockEntityTag(blockposition, world, entityhuman, itemstack, iblockdata1);
+ BlockItem.updateBlockEntityComponents(world, blockposition, itemstack);
++ } catch (Exception e) {
++ oldBlockstate.update(true, false);
++ if (entityhuman instanceof ServerPlayer player) {
++ org.apache.logging.log4j.LogManager.getLogger().error("Player {} tried placing invalid block", player.getScoreboardName(), e);
++ player.getBukkitEntity().kickPlayer("Packet processing error");
++ return InteractionResult.FAIL;
++ }
++ throw e; // Rethrow exception if not placed by a player
++ }
++ // Paper end - Reset placed block on exception
+ iblockdata1.getBlock().setPlacedBy(world, blockposition, iblockdata1, entityhuman, itemstack);
+ // CraftBukkit start
+ if (blockstate != null) {
diff --git a/patches/server/0642-Add-configurable-height-for-slime-spawn.patch b/patches/server/0642-Add-configurable-height-for-slime-spawn.patch
new file mode 100644
index 0000000000..dfc8ecb349
--- /dev/null
+++ b/patches/server/0642-Add-configurable-height-for-slime-spawn.patch
@@ -0,0 +1,35 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Doc <[email protected]>
+Date: Mon, 2 Aug 2021 11:24:39 -0400
+Subject: [PATCH] Add configurable height for slime spawn
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Slime.java b/src/main/java/net/minecraft/world/entity/monster/Slime.java
+index 129f0cbc0469cb2804db6088b53347d88d91f4eb..72346a7e5269c91e3143933ac37e65ad9639b791 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Slime.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java
+@@ -340,7 +340,11 @@ public class Slime extends Mob implements Enemy {
+ return checkMobSpawnRules(type, world, spawnReason, pos, random);
+ }
+
+- if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > 50 && pos.getY() < 70 && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) {
++ // Paper start - Replace rules for Height in Swamp Biome
++ final double maxHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.maximum;
++ final double minHeightSwamp = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.surfaceBiome.minimum;
++ if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > minHeightSwamp && pos.getY() < maxHeightSwamp && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) {
++ // Paper end - Replace rules for Height in Swamp Biome
+ return checkMobSpawnRules(type, world, spawnReason, pos, random);
+ }
+
+@@ -351,7 +355,10 @@ public class Slime extends Mob implements Enemy {
+ ChunkPos chunkcoordintpair = new ChunkPos(pos);
+ boolean flag = world.getMinecraftWorld().paperConfig().entities.spawning.allChunksAreSlimeChunks || WorldgenRandom.seedSlimeChunk(chunkcoordintpair.x, chunkcoordintpair.z, ((WorldGenLevel) world).getSeed(), world.getMinecraftWorld().spigotConfig.slimeSeed).nextInt(10) == 0; // Spigot // Paper
+
+- if (random.nextInt(10) == 0 && flag && pos.getY() < 40) {
++ // Paper start - Replace rules for Height in Slime Chunks
++ final double maxHeightSlimeChunk = world.getMinecraftWorld().paperConfig().entities.spawning.slimeSpawnHeight.slimeChunk.maximum;
++ if (random.nextInt(10) == 0 && flag && pos.getY() < maxHeightSlimeChunk) {
++ // Paper end - Replace rules for Height in Slime Chunks
+ return checkMobSpawnRules(type, world, spawnReason, pos, random);
+ }
+ }
diff --git a/patches/server/0643-Fix-xp-reward-for-baby-zombies.patch b/patches/server/0643-Fix-xp-reward-for-baby-zombies.patch
new file mode 100644
index 0000000000..3ec320760f
--- /dev/null
+++ b/patches/server/0643-Fix-xp-reward-for-baby-zombies.patch
@@ -0,0 +1,32 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 16 Jan 2022 10:34:02 -0800
+Subject: [PATCH] Fix xp reward for baby zombies
+
+The field that tracks the xpReward was not
+getting reset if the death was cancelled
+so this resets it after each call to
+Zombie#getExperienceReward
+
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
+index a835ec6e063dd247a008da84446f8647f38d89d4..94b3ba2688676e92d9d093b63d92cab39d5d2f02 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
+@@ -174,11 +174,16 @@ public class Zombie extends Monster {
+
+ @Override
+ protected int getBaseExperienceReward(ServerLevel world) {
++ final int previousReward = this.xpReward; // Paper - store previous value to reset after calculating XP reward
+ if (this.isBaby()) {
+ this.xpReward = (int) ((double) this.xpReward * 2.5D);
+ }
+
+- return super.getBaseExperienceReward(world);
++ // Paper start - store previous value to reset after calculating XP reward
++ int reward = super.getBaseExperienceReward(world);
++ this.xpReward = previousReward;
++ return reward;
++ // Paper end - store previous value to reset after calculating XP reward
+ }
+
+ @Override
diff --git a/patches/server/0644-Multi-Block-Change-API-Implementation.patch b/patches/server/0644-Multi-Block-Change-API-Implementation.patch
new file mode 100644
index 0000000000..ae2c0b83d4
--- /dev/null
+++ b/patches/server/0644-Multi-Block-Change-API-Implementation.patch
@@ -0,0 +1,62 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Brody Beckwith <[email protected]>
+Date: Fri, 14 Jan 2022 00:41:11 -0500
+Subject: [PATCH] Multi Block Change API Implementation
+
+
+diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
+index 926ff9be3d9e3f5d620e4c7ccb22b9f64865ff8c..1a37654aff9a9c86c9f7af10a1cf721371f0c5ec 100644
+--- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
++++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket.java
+@@ -62,6 +62,14 @@ public class ClientboundSectionBlocksUpdatePacket implements Packet<ClientGamePa
+
+ }
+
++ // Paper start - Multi Block Change API
++ public ClientboundSectionBlocksUpdatePacket(SectionPos sectionPos, it.unimi.dsi.fastutil.shorts.Short2ObjectMap<BlockState> blockChanges) {
++ this.sectionPos = sectionPos;
++ this.positions = blockChanges.keySet().toShortArray();
++ this.states = blockChanges.values().toArray(new BlockState[0]);
++ }
++ // Paper end - Multi Block Change API
++
+ private void write(FriendlyByteBuf buf) {
+ buf.writeLong(this.sectionPos.asLong());
+ buf.writeVarInt(this.positions.length);
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+index f363f885b3dc1852b09914f034740794e3025d3d..eb48efa038043dacf539811de9e0f0faa1ec6b42 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+@@ -937,6 +937,32 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+ this.getHandle().connection.send(packet);
+ }
+
++ // Paper start
++ @Override
++ public void sendMultiBlockChange(final Map<? extends io.papermc.paper.math.Position, BlockData> blockChanges) {
++ if (this.getHandle().connection == null) return;
++
++ Map<SectionPos, it.unimi.dsi.fastutil.shorts.Short2ObjectMap<net.minecraft.world.level.block.state.BlockState>> sectionMap = new HashMap<>();
++
++ for (Map.Entry<? extends io.papermc.paper.math.Position, BlockData> entry : blockChanges.entrySet()) {
++ BlockData blockData = entry.getValue();
++ BlockPos blockPos = io.papermc.paper.util.MCUtil.toBlockPos(entry.getKey());
++ SectionPos sectionPos = SectionPos.of(blockPos);
++
++ it.unimi.dsi.fastutil.shorts.Short2ObjectMap<net.minecraft.world.level.block.state.BlockState> sectionData = sectionMap.computeIfAbsent(sectionPos, key -> new it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap<>());
++ sectionData.put(SectionPos.sectionRelativePos(blockPos), ((CraftBlockData) blockData).getState());
++ }
++
++ for (Map.Entry<SectionPos, it.unimi.dsi.fastutil.shorts.Short2ObjectMap<net.minecraft.world.level.block.state.BlockState>> entry : sectionMap.entrySet()) {
++ SectionPos sectionPos = entry.getKey();
++ it.unimi.dsi.fastutil.shorts.Short2ObjectMap<net.minecraft.world.level.block.state.BlockState> blockData = entry.getValue();
++
++ net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket packet = new net.minecraft.network.protocol.game.ClientboundSectionBlocksUpdatePacket(sectionPos, blockData);
++ this.getHandle().connection.send(packet);
++ }
++ }
++ // Paper end
++
+ @Override
+ public void sendBlockChanges(Collection<BlockState> blocks) {
+ Preconditions.checkArgument(blocks != null, "blocks must not be null");
diff --git a/patches/server/0645-Fix-NotePlayEvent.patch b/patches/server/0645-Fix-NotePlayEvent.patch
new file mode 100644
index 0000000000..cdffcd6f0f
--- /dev/null
+++ b/patches/server/0645-Fix-NotePlayEvent.patch
@@ -0,0 +1,54 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Kieran Wallbanks <[email protected]>
+Date: Mon, 21 Jun 2021 14:23:50 +0100
+Subject: [PATCH] Fix NotePlayEvent
+
+== AT ==
+public org.bukkit.craftbukkit.block.data.CraftBlockData toNMS(Ljava/lang/Enum;Ljava/lang/Class;)Ljava/lang/Enum;
+
+diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java
+index 57e13269367a82ec39c2298b20d7595f61326f47..71fd7a467a4cb89cad8d2541366fd4add9115e04 100644
+--- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java
+@@ -96,11 +96,12 @@ public class NoteBlock extends Block {
+ private void playNote(@Nullable Entity entity, BlockState state, Level world, BlockPos pos) {
+ if (((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).worksAboveNoteBlock() || world.getBlockState(pos.above()).isAir()) {
+ // CraftBukkit start
+- org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE));
+- if (event.isCancelled()) {
+- return;
+- }
++ // org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(NoteBlock.INSTRUMENT), state.getValue(NoteBlock.NOTE));
++ // if (event.isCancelled()) {
++ // return;
++ // }
+ // CraftBukkit end
++ // Paper - move NotePlayEvent call to fix instrument/note changes; TODO any way to cancel the game event?
+ world.blockEvent(pos, this, 0, 0);
+ world.gameEvent(entity, (Holder) GameEvent.NOTE_BLOCK_PLAY, pos);
+ }
+@@ -139,10 +140,14 @@ public class NoteBlock extends Block {
+ @Override
+ protected boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) {
+ NoteBlockInstrument blockpropertyinstrument = (NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT);
++ // Paper start - move NotePlayEvent call to fix instrument/note changes
++ org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, blockpropertyinstrument, state.getValue(NOTE));
++ if (event.isCancelled()) return false;
++ // Paper end - move NotePlayEvent call to fix instrument/note changes
+ float f;
+
+ if (blockpropertyinstrument.isTunable()) {
+- int k = (Integer) state.getValue(NoteBlock.NOTE);
++ int k = event.getNote().getId(); // Paper - move NotePlayEvent call to fix instrument/note changes
+
+ f = NoteBlock.getPitchFromNote(k);
+ world.addParticle(ParticleTypes.NOTE, (double) pos.getX() + 0.5D, (double) pos.getY() + 1.2D, (double) pos.getZ() + 0.5D, (double) k / 24.0D, 0.0D, 0.0D);
+@@ -161,7 +166,7 @@ public class NoteBlock extends Block {
+
+ holder = Holder.direct(SoundEvent.createVariableRangeEvent(minecraftkey));
+ } else {
+- holder = blockpropertyinstrument.getSoundEvent();
++ holder = org.bukkit.craftbukkit.block.data.CraftBlockData.toNMS(event.getInstrument(), NoteBlockInstrument.class).getSoundEvent(); // Paper - move NotePlayEvent call to fix instrument/note changes
+ }
+
+ world.playSeededSound((Player) null, (double) pos.getX() + 0.5D, (double) pos.getY() + 0.5D, (double) pos.getZ() + 0.5D, holder, SoundSource.RECORDS, 3.0F, f, world.random.nextLong());
diff --git a/patches/server/0646-Freeze-Tick-Lock-API.patch b/patches/server/0646-Freeze-Tick-Lock-API.patch
new file mode 100644
index 0000000000..74446f0546
--- /dev/null
+++ b/patches/server/0646-Freeze-Tick-Lock-API.patch
@@ -0,0 +1,82 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Sun, 26 Dec 2021 20:27:43 -0500
+Subject: [PATCH] Freeze Tick Lock API
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 17c8a369e5a09276a3918dfb2bb004441e35a8e0..4784bfb23a7e0004287f89213d50bcfaaaed9e38 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -415,6 +415,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ private org.bukkit.util.Vector origin;
+ @javax.annotation.Nullable
+ private UUID originWorld;
++ public boolean freezeLocked = false; // Paper - Freeze Tick Lock API
+
+ public void setOrigin(@javax.annotation.Nonnull Location location) {
+ this.origin = location.toVector();
+@@ -764,7 +765,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ this.setRemainingFireTicks(this.remainingFireTicks - 1);
+ }
+
+- if (this.getTicksFrozen() > 0) {
++ if (this.getTicksFrozen() > 0 && !freezeLocked) { // Paper - Freeze Tick Lock API
+ this.setTicksFrozen(0);
+ this.level().levelEvent((Player) null, 1009, this.blockPosition, 1);
+ }
+@@ -2452,6 +2453,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ if (fromNetherPortal) {
+ nbttagcompound.putBoolean("Paper.FromNetherPortal", true);
+ }
++ if (freezeLocked) {
++ nbttagcompound.putBoolean("Paper.FreezeLock", true);
++ }
+ // Paper end
+ return nbttagcompound;
+ } catch (Throwable throwable) {
+@@ -2599,6 +2603,9 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ if (spawnReason == null) {
+ spawnReason = org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.DEFAULT;
+ }
++ if (nbt.contains("Paper.FreezeLock")) {
++ freezeLocked = nbt.getBoolean("Paper.FreezeLock");
++ }
+ // Paper end
+
+ } catch (Throwable throwable) {
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index dfa1e3548f9b48c20f28712ecb06310fe9c856f5..bb96b962439aa62954352f193b44f97d2e826373 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -3621,7 +3621,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+ this.calculateEntityAnimation(this instanceof FlyingAnimal);
+ gameprofilerfiller.pop();
+ gameprofilerfiller.push("freezing");
+- if (!this.level().isClientSide && !this.isDeadOrDying()) {
++ if (!this.level().isClientSide && !this.isDeadOrDying() && !this.freezeLocked) { // Paper - Freeze Tick Lock API
+ int i = this.getTicksFrozen();
+
+ if (this.isInPowderSnow && this.canFreeze()) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+index 442b5f13e976dd63bf1dccc12eb8c3f16314c581..10fb64df10820974d11f142c102a11a5bd0f317c 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
+@@ -324,6 +324,17 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
+ return this.getHandle().isFullyFrozen();
+ }
+
++ // Paper start - Freeze Tick Lock API
++ @Override
++ public boolean isFreezeTickingLocked() {
++ return this.entity.freezeLocked;
++ }
++
++ @Override
++ public void lockFreezeTicks(boolean locked) {
++ this.entity.freezeLocked = locked;
++ }
++ // Paper end - Freeze Tick Lock API
+ @Override
+ public void remove() {
+ this.entity.pluginRemoved = true;
diff --git a/patches/server/0647-More-PotionEffectType-API.patch b/patches/server/0647-More-PotionEffectType-API.patch
new file mode 100644
index 0000000000..2773c98338
--- /dev/null
+++ b/patches/server/0647-More-PotionEffectType-API.patch
@@ -0,0 +1,98 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 27 May 2021 21:58:24 -0700
+Subject: [PATCH] More PotionEffectType API
+
+== AT ==
+public net.minecraft.world.effect.MobEffect attributeModifiers
+public net.minecraft.world.effect.MobEffect$AttributeTemplate
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java
+index 21d4224c8993f521d6004d708ecbf71fa6d09306..6cf790c9fa23ea313423fdaeb7c181bf530828c6 100644
+--- a/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java
++++ b/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java
+@@ -129,6 +129,48 @@ public class CraftPotionEffectType extends PotionEffectType implements Handleabl
+ return this.handle.getDescriptionId();
+ }
+
++ // Paper start
++ @Override
++ public java.util.Map<org.bukkit.attribute.Attribute, org.bukkit.attribute.AttributeModifier> getEffectAttributes() {
++ // re-create map each time because a nms MobEffect can have its attributes modified
++ final java.util.Map<org.bukkit.attribute.Attribute, org.bukkit.attribute.AttributeModifier> attributeMap = new java.util.HashMap<>();
++ this.handle.attributeModifiers.forEach((attribute, attributeModifier) -> {
++ attributeMap.put(
++ org.bukkit.craftbukkit.attribute.CraftAttribute.minecraftHolderToBukkit(attribute),
++ // use zero as amplifier to get the base amount, as it is amount = base * (amplifier + 1)
++ org.bukkit.craftbukkit.attribute.CraftAttributeInstance.convert(attributeModifier.create(0))
++ );
++ });
++ return java.util.Map.copyOf(attributeMap);
++ }
++
++ @Override
++ public double getAttributeModifierAmount(org.bukkit.attribute.Attribute attribute, int effectAmplifier) {
++ com.google.common.base.Preconditions.checkArgument(effectAmplifier >= 0, "effectAmplifier must be greater than or equal to 0");
++ Holder<net.minecraft.world.entity.ai.attributes.Attribute> nmsAttribute = org.bukkit.craftbukkit.attribute.CraftAttribute.bukkitToMinecraftHolder(attribute);
++ com.google.common.base.Preconditions.checkArgument(this.handle.attributeModifiers.containsKey(nmsAttribute), attribute + " is not present on " + this.getKey());
++ return this.handle.attributeModifiers.get(nmsAttribute).create(effectAmplifier).amount();
++ }
++
++ @Override
++ public PotionEffectType.Category getEffectCategory() {
++ return fromNMS(handle.getCategory());
++ }
++
++ @Override
++ public String translationKey() {
++ return this.handle.getDescriptionId();
++ }
++
++ public static PotionEffectType.Category fromNMS(net.minecraft.world.effect.MobEffectCategory mobEffectInfo) {
++ return switch (mobEffectInfo) {
++ case BENEFICIAL -> PotionEffectType.Category.BENEFICIAL;
++ case HARMFUL -> PotionEffectType.Category.HARMFUL;
++ case NEUTRAL -> PotionEffectType.Category.NEUTRAL;
++ };
++ }
++ // Paper end
++
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+diff --git a/src/test/java/io/papermc/paper/effects/EffectCategoryTest.java b/src/test/java/io/papermc/paper/effects/EffectCategoryTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..a57e8fdc35efc7e0353d4f36c91578390ee4572e
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/effects/EffectCategoryTest.java
+@@ -0,0 +1,30 @@
++package io.papermc.paper.effects;
++
++import io.papermc.paper.adventure.PaperAdventure;
++import net.minecraft.world.effect.MobEffectCategory;
++import org.bukkit.craftbukkit.potion.CraftPotionEffectType;
++import org.bukkit.potion.PotionEffectType;
++import org.bukkit.support.environment.AllFeatures;
++import org.junit.jupiter.api.Test;
++
++import static org.junit.jupiter.api.Assertions.assertEquals;
++import static org.junit.jupiter.api.Assertions.assertNotNull;
++
++@AllFeatures
++public class EffectCategoryTest {
++
++ @Test
++ public void testEffectCategoriesExist() {
++ for (MobEffectCategory mobEffectInfo : MobEffectCategory.values()) {
++ assertNotNull(CraftPotionEffectType.fromNMS(mobEffectInfo), mobEffectInfo + " is missing a bukkit equivalent");
++ }
++ }
++
++ @Test
++ public void testCategoryHasEquivalentColors() {
++ for (MobEffectCategory mobEffectInfo : MobEffectCategory.values()) {
++ PotionEffectType.Category bukkitEffectCategory = CraftPotionEffectType.fromNMS(mobEffectInfo);
++ assertEquals(bukkitEffectCategory.getColor(), PaperAdventure.asAdventure(mobEffectInfo.getTooltipFormatting()), mobEffectInfo.getTooltipFormatting().name() + " doesn't equal " + bukkitEffectCategory.getColor());
++ }
++ }
++}
diff --git a/patches/server/0648-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch b/patches/server/0648-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch
new file mode 100644
index 0000000000..027285c2b1
--- /dev/null
+++ b/patches/server/0648-Use-a-CHM-for-StructureTemplate.Pallete-cache.patch
@@ -0,0 +1,20 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Shane Freeder <[email protected]>
+Date: Mon, 12 Jul 2021 12:28:29 +0100
+Subject: [PATCH] Use a CHM for StructureTemplate.Pallete cache
+
+fixes a CME due to this collection being shared across threads
+
+diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
+index e7900e7e8263cae131dad2427fb2bfdadf5b8d14..b120949667ae0169a667b329b3cabbd79a0a5bda 100644
+--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
+@@ -889,7 +889,7 @@ public class StructureTemplate {
+ public static final class Palette {
+
+ private final List<StructureTemplate.StructureBlockInfo> blocks;
+- private final Map<Block, List<StructureTemplate.StructureBlockInfo>> cache = Maps.newHashMap();
++ private final Map<Block, List<StructureTemplate.StructureBlockInfo>> cache = Maps.newConcurrentMap(); // Paper - Fix CME due to this collection being shared across threads
+ @Nullable
+ private List<StructureTemplate.JigsawBlockInfo> cachedJigsaws;
+
diff --git a/patches/server/0649-API-for-creating-command-sender-which-forwards-feedb.patch b/patches/server/0649-API-for-creating-command-sender-which-forwards-feedb.patch
new file mode 100644
index 0000000000..f86274766d
--- /dev/null
+++ b/patches/server/0649-API-for-creating-command-sender-which-forwards-feedb.patch
@@ -0,0 +1,170 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Tue, 1 Feb 2022 15:51:55 -0700
+Subject: [PATCH] API for creating command sender which forwards feedback
+
+
+diff --git a/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..e3a5f1ec376319bdfda87fa27ae217bff3914292
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/commands/FeedbackForwardingSender.java
+@@ -0,0 +1,111 @@
++package io.papermc.paper.commands;
++
++import io.papermc.paper.adventure.PaperAdventure;
++import java.util.UUID;
++import java.util.function.Consumer;
++import net.kyori.adventure.audience.MessageType;
++import net.kyori.adventure.identity.Identity;
++import net.kyori.adventure.text.Component;
++import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
++import net.minecraft.commands.CommandSource;
++import net.minecraft.commands.CommandSourceStack;
++import net.minecraft.server.level.ServerLevel;
++import net.minecraft.world.phys.Vec2;
++import net.minecraft.world.phys.Vec3;
++import org.bukkit.command.CommandSender;
++import org.bukkit.craftbukkit.CraftServer;
++import org.bukkit.craftbukkit.command.ServerCommandSender;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public final class FeedbackForwardingSender extends ServerCommandSender {
++ private final Consumer<? super Component> feedback;
++ private final CraftServer server;
++
++ public FeedbackForwardingSender(final Consumer<? super Component> feedback, final CraftServer server) {
++ super(((ServerCommandSender) server.getConsoleSender()).perm);
++ this.server = server;
++ this.feedback = feedback;
++ }
++
++ @Override
++ public void sendMessage(final String message) {
++ this.sendMessage(LegacyComponentSerializer.legacySection().deserialize(message));
++ }
++
++ @Override
++ public void sendMessage(final String... messages) {
++ for (final String message : messages) {
++ this.sendMessage(message);
++ }
++ }
++
++ @Override
++ public void sendMessage(final Identity identity, final Component message, final MessageType type) {
++ this.feedback.accept(message);
++ }
++
++ @Override
++ public String getName() {
++ return "FeedbackForwardingSender";
++ }
++
++ @Override
++ public Component name() {
++ return Component.text(this.getName());
++ }
++
++ @Override
++ public boolean isOp() {
++ return true;
++ }
++
++ @Override
++ public void setOp(final boolean value) {
++ throw new UnsupportedOperationException("Cannot change operator status of " + this.getClass().getName());
++ }
++
++ public CommandSourceStack asVanilla() {
++ final @Nullable ServerLevel overworld = this.server.getServer().overworld();
++ return new CommandSourceStack(
++ new Source(this),
++ overworld == null ? Vec3.ZERO : Vec3.atLowerCornerOf(overworld.getSharedSpawnPos()),
++ Vec2.ZERO,
++ overworld,
++ 4,
++ this.getName(),
++ net.minecraft.network.chat.Component.literal(this.getName()),
++ this.server.getServer(),
++ null
++ );
++ }
++
++ private record Source(FeedbackForwardingSender sender) implements CommandSource {
++ @Override
++ public void sendSystemMessage(final net.minecraft.network.chat.Component message) {
++ this.sender.sendMessage(Identity.nil(), PaperAdventure.asAdventure(message));
++ }
++
++ @Override
++ public boolean acceptsSuccess() {
++ return true;
++ }
++
++ @Override
++ public boolean acceptsFailure() {
++ return true;
++ }
++
++ @Override
++ public boolean shouldInformAdmins() {
++ return false;
++ }
++
++ @Override
++ public CommandSender getBukkitSender(final CommandSourceStack stack) {
++ return this.sender;
++ }
++ }
++}
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 62ab88e022230d25ffb359981ce7da4e64a9be5a..3b01907bc119853e0676e912e9a29b05d9aa5763 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -2165,6 +2165,13 @@ public final class CraftServer implements Server {
+ return this.console.console;
+ }
+
++ // Paper start
++ @Override
++ public CommandSender createCommandSender(final java.util.function.Consumer<? super net.kyori.adventure.text.Component> feedback) {
++ return new io.papermc.paper.commands.FeedbackForwardingSender(feedback, this);
++ }
++ // Paper end
++
+ public EntityMetadataStore getEntityMetadata() {
+ return this.entityMetadata;
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java
+index 7f22950ae61436e91a59cd29a345809c42bbe739..1e3091687735b461d3b6a313ab8761127981d3e8 100644
+--- a/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java
++++ b/src/main/java/org/bukkit/craftbukkit/command/ServerCommandSender.java
+@@ -12,7 +12,7 @@ import org.bukkit.permissions.PermissionAttachmentInfo;
+ import org.bukkit.plugin.Plugin;
+
+ public abstract class ServerCommandSender implements CommandSender {
+- private final PermissibleBase perm;
++ public final PermissibleBase perm; // Paper
+ private net.kyori.adventure.pointer.Pointers adventure$pointers; // Paper - implement pointers
+
+ protected ServerCommandSender() {
+diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+index 35b05f9321ddbbbdf62f4bf726b58cf1205f0cfb..ce8683eff5b8ade57a2fcb77027cfe4b26986bc7 100644
+--- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
++++ b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java
+@@ -86,6 +86,11 @@ public final class VanillaCommandWrapper extends BukkitCommand {
+ if (sender instanceof ProxiedCommandSender) {
+ return ((ProxiedNativeCommandSender) sender).getHandle();
+ }
++ // Paper start
++ if (sender instanceof io.papermc.paper.commands.FeedbackForwardingSender feedback) {
++ return feedback.asVanilla();
++ }
++ // Paper end
+
+ throw new IllegalArgumentException("Cannot make " + sender + " a vanilla command listener");
+ }
diff --git a/patches/server/0650-Add-missing-structure-set-seed-configs.patch b/patches/server/0650-Add-missing-structure-set-seed-configs.patch
new file mode 100644
index 0000000000..e2dfe31280
--- /dev/null
+++ b/patches/server/0650-Add-missing-structure-set-seed-configs.patch
@@ -0,0 +1,399 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 13 Jan 2022 23:05:53 -0800
+Subject: [PATCH] Add missing structure set seed configs
+
+The 4 missing structure set seed configs are strongholds, mineshafts,
+buried treasure, and ancient cities.
+
+Strongholds use a ring placement scheme which isn't random so they
+utilize the world seed by default, this adds a config to override it
+for just generating the ring positions.
+
+Mineshafts and Buried Treasure structure sets are special cases
+where the "salt" that can be defined for them via datapacks has 0
+effect because the difference between the spacing and separation is 1
+which is used as the upper bound in the random with salt. So the random
+always returns the same int (0) so the salt has no effect. This adds
+seeds/salts to the frequency reducer which has a similar effect.
+
+Co-authored-by: William Blake Galbreath <[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 fde17b4e2607fc443a33aea3a631aae6ccb71e2c..c64389acf0764c8d048bea2d99a21a0da832150d 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
+@@ -574,7 +574,7 @@ public abstract class ChunkGenerator {
+ }
+ }
+
+- if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z)) {
++ if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z, structureplacement instanceof net.minecraft.world.level.chunk.ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs
+ if (list.size() == 1) {
+ this.tryGenerateStructure((StructureSet.StructureSelectionEntry) list.get(0), structureAccessor, registryManager, randomstate, structureTemplateManager, placementCalculator.getLevelSeed(), chunk, chunkcoordintpair, sectionposition, dimension);
+ } else {
+diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java
+index ed779a03e2ce7684428600dac4f2e92ab002f29f..a20520a6bd28bae1cee82258ac49d9753faba2bd 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java
++++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java
+@@ -50,13 +50,14 @@ public class ChunkGeneratorStructureState {
+ private final Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> ringPositions = new Object2ObjectArrayMap();
+ private boolean hasGeneratedPositions;
+ private final List<Holder<StructureSet>> possibleStructureSets;
++ public final SpigotWorldConfig conf; // Paper - Add missing structure set seed configs
+
+ public static ChunkGeneratorStructureState createForFlat(RandomState randomstate, long i, BiomeSource worldchunkmanager, Stream<Holder<StructureSet>> stream, SpigotWorldConfig conf) { // Spigot
+ List<Holder<StructureSet>> list = stream.filter((holder) -> {
+ return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder.value(), worldchunkmanager);
+ }).toList();
+
+- return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf)); // Spigot
++ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot
+ }
+
+ public static ChunkGeneratorStructureState createForNormal(RandomState randomstate, long i, BiomeSource worldchunkmanager, HolderLookup<StructureSet> holderlookup, SpigotWorldConfig conf) { // Spigot
+@@ -64,14 +65,24 @@ public class ChunkGeneratorStructureState {
+ return ChunkGeneratorStructureState.hasBiomesForStructureSet((StructureSet) holder_c.value(), worldchunkmanager);
+ }).collect(Collectors.toUnmodifiableList());
+
+- return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, i, ChunkGeneratorStructureState.injectSpigot(list, conf)); // Spigot
++ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, i, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot
+ }
++ // Paper start - Add missing structure set seed configs; horrible hack because spigot creates a ton of direct Holders which lose track of the identifying key
++ public static final class KeyedRandomSpreadStructurePlacement extends RandomSpreadStructurePlacement {
++ public final net.minecraft.resources.ResourceKey<StructureSet> key;
++ public KeyedRandomSpreadStructurePlacement(net.minecraft.resources.ResourceKey<StructureSet> key, net.minecraft.core.Vec3i locateOffset, FrequencyReductionMethod frequencyReductionMethod, float frequency, int salt, java.util.Optional<StructurePlacement.ExclusionZone> exclusionZone, int spacing, int separation, net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType spreadType) {
++ super(locateOffset, frequencyReductionMethod, frequency, salt, exclusionZone, spacing, separation, spreadType);
++ this.key = key;
++ }
++ }
++ // Paper end - Add missing structure set seed configs
+
+ // Spigot start
+ private static List<Holder<StructureSet>> injectSpigot(List<Holder<StructureSet>> list, SpigotWorldConfig conf) {
+ return list.stream().map((holder) -> {
+ StructureSet structureset = holder.value();
+- if (structureset.placement() instanceof RandomSpreadStructurePlacement randomConfig) {
++ final Holder<StructureSet> newHolder; // Paper - Add missing structure set seed configs
++ if (structureset.placement() instanceof RandomSpreadStructurePlacement randomConfig && holder.unwrapKey().orElseThrow().location().getNamespace().equals(net.minecraft.resources.ResourceLocation.DEFAULT_NAMESPACE)) { // Paper - Add missing structure set seed configs; check namespace cause datapacks could add structure sets with the same path
+ String name = holder.unwrapKey().orElseThrow().location().getPath();
+ int seed = randomConfig.salt;
+
+@@ -118,11 +129,27 @@ public class ChunkGeneratorStructureState {
+ case "villages":
+ seed = conf.villageSeed;
+ break;
++ // Paper start - Add missing structure set seed configs
++ case "ancient_cities":
++ seed = conf.ancientCitySeed;
++ break;
++ case "trail_ruins":
++ seed = conf.trailRuinsSeed;
++ break;
++ case "trial_chambers":
++ seed = conf.trialChambersSeed;
++ break;
++ // Paper end - Add missing structure set seed configs
+ }
+
+- structureset = new StructureSet(structureset.structures(), new RandomSpreadStructurePlacement(randomConfig.locateOffset, randomConfig.frequencyReductionMethod, randomConfig.frequency, seed, randomConfig.exclusionZone, randomConfig.spacing(), randomConfig.separation(), randomConfig.spreadType()));
++ // Paper start - Add missing structure set seed configs
++ structureset = new StructureSet(structureset.structures(), new KeyedRandomSpreadStructurePlacement(holder.unwrapKey().orElseThrow(), randomConfig.locateOffset, randomConfig.frequencyReductionMethod, randomConfig.frequency, seed, randomConfig.exclusionZone, randomConfig.spacing(), randomConfig.separation(), randomConfig.spreadType()));
++ newHolder = Holder.direct(structureset); // I really wish we didn't have to do this here
++ } else {
++ newHolder = holder;
+ }
+- return Holder.direct(structureset);
++ return newHolder;
++ // Paper end - Add missing structure set seed configs
+ }).collect(Collectors.toUnmodifiableList());
+ }
+ // Spigot end
+@@ -139,12 +166,13 @@ public class ChunkGeneratorStructureState {
+ return stream.anyMatch(set::contains);
+ }
+
+- private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List<Holder<StructureSet>> structureSets) {
++ private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List<Holder<StructureSet>> structureSets, SpigotWorldConfig conf) { // Paper - Add missing structure set seed configs
+ this.randomState = noiseConfig;
+ this.levelSeed = structureSeed;
+ this.biomeSource = biomeSource;
+ this.concentricRingsSeed = concentricRingSeed;
+ this.possibleStructureSets = structureSets;
++ this.conf = conf; // Paper - Add missing structure set seed configs
+ }
+
+ public List<Holder<StructureSet>> possibleStructureSets() {
+@@ -198,7 +226,13 @@ public class ChunkGeneratorStructureState {
+ HolderSet<Biome> holderset = placement.preferredBiomes();
+ RandomSource randomsource = RandomSource.create();
+
++ // Paper start - Add missing structure set seed configs
++ if (this.conf.strongholdSeed != null && structureSetEntry.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) {
++ randomsource.setSeed(this.conf.strongholdSeed);
++ } else {
++ // Paper end - Add missing structure set seed configs
+ randomsource.setSeed(this.concentricRingsSeed);
++ } // Paper - Add missing structure set seed configs
+ double d0 = randomsource.nextDouble() * Math.PI * 2.0D;
+ int l = 0;
+ int i1 = 0;
+@@ -275,7 +309,7 @@ public class ChunkGeneratorStructureState {
+
+ for (int l = centerChunkX - chunkCount; l <= centerChunkX + chunkCount; ++l) {
+ for (int i1 = centerChunkZ - chunkCount; i1 <= centerChunkZ + chunkCount; ++i1) {
+- if (structureplacement.isStructureChunk(this, l, i1)) {
++ if (structureplacement.isStructureChunk(this, l, i1, structureplacement instanceof KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs
+ return true;
+ }
+ }
+diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
+index 953ab7638f7242b5a11dd1de8786172443a0558c..5f354b333a39b873915bedd57b647355ae5bdf56 100644
+--- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
+@@ -74,6 +74,20 @@ public class StructureCheck {
+ this.fixerUpper = dataFixer;
+ }
+
++ // Paper start - add missing structure salt configs
++ @Nullable
++ private Integer getSaltOverride(Structure type) {
++ if (this.heightAccessor instanceof net.minecraft.server.level.ServerLevel serverLevel) {
++ if (type instanceof net.minecraft.world.level.levelgen.structure.structures.MineshaftStructure) {
++ return serverLevel.spigotConfig.mineshaftSeed;
++ } else if (type instanceof net.minecraft.world.level.levelgen.structure.structures.BuriedTreasureStructure) {
++ return serverLevel.spigotConfig.buriedTreasureSeed;
++ }
++ }
++ return null;
++ }
++ // Paper end - add missing structure seed configs
++
+ public StructureCheckResult checkStart(ChunkPos pos, Structure type, StructurePlacement placement, boolean skipReferencedStructures) {
+ long l = pos.toLong();
+ Object2IntMap<Structure> object2IntMap = this.loadedChunks.get(l);
+@@ -83,7 +97,7 @@ public class StructureCheck {
+ StructureCheckResult structureCheckResult = this.tryLoadFromStorage(pos, type, skipReferencedStructures, l);
+ if (structureCheckResult != null) {
+ return structureCheckResult;
+- } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed)) {
++ } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed, this.getSaltOverride(type))) { // Paper - add missing structure seed configs
+ return StructureCheckResult.START_NOT_PRESENT;
+ } else {
+ boolean bl = this.featureChecks
+diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java
+index a4c34e9415632354d33668a38d06453ada4d3c77..cbf13e4f2da6a27619e9bc9a7cd73bb6e69cad2a 100644
+--- a/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java
++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java
+@@ -79,14 +79,30 @@ public abstract class StructurePlacement {
+ return this.exclusionZone;
+ }
+
++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add missing structure set seed configs
+ public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ) {
++ // Paper start - Add missing structure set seed configs
++ return this.isStructureChunk(calculator, chunkX, chunkZ, null);
++ }
++ public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ, @org.jetbrains.annotations.Nullable net.minecraft.resources.ResourceKey<StructureSet> structureSetKey) {
++ Integer saltOverride = null;
++ if (structureSetKey != null) {
++ if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.MINESHAFTS) {
++ saltOverride = calculator.conf.mineshaftSeed;
++ } else if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.BURIED_TREASURES) {
++ saltOverride = calculator.conf.buriedTreasureSeed;
++ }
++ }
++ // Paper end - Add missing structure set seed configs
+ return this.isPlacementChunk(calculator, chunkX, chunkZ)
+- && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed())
++ && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed(), saltOverride) // Paper - Add missing structure set seed configs
+ && this.applyInteractionsWithOtherStructures(calculator, chunkX, chunkZ);
+ }
+
+- public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed) {
+- return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency);
++ // Paper start - Add missing structure set seed configs
++ public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed, @org.jetbrains.annotations.Nullable Integer saltOverride) {
++ return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency, saltOverride);
++ // Paper end - Add missing structure set seed configs
+ }
+
+ public boolean applyInteractionsWithOtherStructures(ChunkGeneratorStructureState calculator, int centerChunkX, int centerChunkZ) {
+@@ -101,25 +117,31 @@ public abstract class StructurePlacement {
+
+ public abstract StructurePlacementType<?> type();
+
+- private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) {
++ private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here
+ WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
+ worldgenRandom.setLargeFeatureWithSalt(seed, salt, chunkX, chunkZ);
+ return worldgenRandom.nextFloat() < frequency;
+ }
+
+- private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency) {
++ private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs
+ WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
++ if (saltOverride == null) { // Paper - Add missing structure set seed configs
+ worldgenRandom.setLargeFeatureSeed(seed, chunkX, chunkZ);
++ // Paper start - Add missing structure set seed configs
++ } else {
++ worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride);
++ }
++ // Paper end - Add missing structure set seed configs
+ return worldgenRandom.nextDouble() < (double)frequency;
+ }
+
+- private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) {
++ private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs
+ WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
+- worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, 10387320);
++ worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride != null ? saltOverride : HIGHLY_ARBITRARY_RANDOM_SALT); // Paper - Add missing structure set seed configs
+ return worldgenRandom.nextFloat() < frequency;
+ }
+
+- private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) {
++ private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here
+ int i = chunkX >> 4;
+ int j = chunkZ >> 4;
+ WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L));
+@@ -147,7 +169,7 @@ public abstract class StructurePlacement {
+
+ @FunctionalInterface
+ public interface FrequencyReducer {
+- boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance);
++ boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride); // Paper - Add missing structure set seed configs
+ }
+
+ public static enum FrequencyReductionMethod implements StringRepresentable {
+@@ -167,8 +189,8 @@ public abstract class StructurePlacement {
+ this.reducer = generationPredicate;
+ }
+
+- public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance) {
+- return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance);
++ public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs
++ return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance, saltOverride); // Paper - Add missing structure set seed configs
+ }
+
+ @Override
+diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java
+index e76f96a5c48d1eda2f9bbb3e11dd79f23f9ab75c..2b263246135c85aa225120519e9702a628773935 100644
+--- a/src/main/java/org/spigotmc/SpigotWorldConfig.java
++++ b/src/main/java/org/spigotmc/SpigotWorldConfig.java
+@@ -322,6 +322,18 @@ public class SpigotWorldConfig
+ public int mansionSeed;
+ public int fossilSeed;
+ public int portalSeed;
++ // Paper start - add missing structure set configs
++ public int ancientCitySeed;
++ public int trailRuinsSeed;
++ public int trialChambersSeed;
++ public int buriedTreasureSeed;
++ public Integer mineshaftSeed;
++ public Long strongholdSeed;
++ private <N extends Number> N getSeed(String path, java.util.function.Function<String, N> toNumberFunc) {
++ final String value = this.getString(path, "default");
++ return org.apache.commons.lang3.math.NumberUtils.isParsable(value) ? toNumberFunc.apply(value) : null;
++ }
++ // Paper end
+ private void initWorldGenSeeds()
+ {
+ this.villageSeed = this.getInt( "seed-village", 10387312 );
+@@ -339,6 +351,14 @@ public class SpigotWorldConfig
+ this.mansionSeed = this.getInt( "seed-mansion", 10387319 );
+ this.fossilSeed = this.getInt( "seed-fossil", 14357921 );
+ this.portalSeed = this.getInt( "seed-portal", 34222645 );
++ // Paper start - add missing structure set configs
++ this.ancientCitySeed = this.getInt("seed-ancientcity", 20083232);
++ this.trailRuinsSeed = this.getInt("seed-trailruins", 83469867);
++ this.trialChambersSeed = this.getInt("seed-trialchambers", 94251327);
++ this.buriedTreasureSeed = this.getInt("seed-buriedtreasure", 10387320); // StructurePlacement#HIGHLY_ARBITRARY_RANDOM_SALT
++ this.mineshaftSeed = this.getSeed("seed-mineshaft", Integer::parseInt);
++ this.strongholdSeed = this.getSeed("seed-stronghold", Long::parseLong);
++ // Paper end
+ this.log( "Custom Map Seeds: Village: " + this.villageSeed + " Desert: " + this.desertSeed + " Igloo: " + this.iglooSeed + " Jungle: " + this.jungleSeed + " Swamp: " + this.swampSeed + " Monument: " + this.monumentSeed
+ + " Ocean: " + this.oceanSeed + " Shipwreck: " + this.shipwreckSeed + " End City: " + this.endCitySeed + " Slime: " + this.slimeSeed + " Nether: " + this.netherSeed + " Mansion: " + this.mansionSeed + " Fossil: " + this.fossilSeed + " Portal: " + this.portalSeed );
+ }
+diff --git a/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java b/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..cc1fb5ae9e0898735771c6163c9b90658fb61eed
+--- /dev/null
++++ b/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java
+@@ -0,0 +1,77 @@
++package io.papermc.paper.world.structure;
++
++import io.papermc.paper.configuration.PaperConfigurations;
++import java.io.File;
++import java.lang.reflect.Field;
++import net.minecraft.core.Registry;
++import net.minecraft.core.registries.Registries;
++import net.minecraft.resources.ResourceKey;
++import net.minecraft.resources.ResourceLocation;
++import net.minecraft.world.level.levelgen.structure.BuiltinStructureSets;
++import net.minecraft.world.level.levelgen.structure.StructureSet;
++import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
++import org.bukkit.configuration.file.YamlConfiguration;
++import org.bukkit.support.RegistryHelper;
++import org.bukkit.support.environment.AllFeatures;
++import org.jetbrains.annotations.NotNull;
++import org.junit.jupiter.api.Test;
++import org.spigotmc.SpigotConfig;
++import org.spigotmc.SpigotWorldConfig;
++
++import static org.junit.jupiter.api.Assertions.assertEquals;
++
++@AllFeatures
++public class StructureSeedConfigTest {
++
++ @Test
++ public void checkStructureSeedDefaults() throws ReflectiveOperationException {
++ SpigotConfig.config = new YamlConfiguration() {
++ @Override
++ public void save(final @NotNull File file) {
++ // no-op
++ }
++ };
++ final SpigotWorldConfig config = PaperConfigurations.SPIGOT_WORLD_DEFAULTS.get();
++
++
++ final Registry<StructureSet> structureSets = RegistryHelper.getRegistry().lookupOrThrow(Registries.STRUCTURE_SET);
++ for (final ResourceKey<StructureSet> setKey : structureSets.registryKeySet()) {
++ assertEquals(ResourceLocation.DEFAULT_NAMESPACE, setKey.location().getNamespace());
++ final StructureSet set = structureSets.getValueOrThrow(setKey);
++ if (setKey == BuiltinStructureSets.STRONGHOLDS) { // special case due to seed matching world seed
++ assertEquals(0, set.placement().salt);
++ continue;
++ }
++ int salt = switch (setKey.location().getPath()) {
++ case "villages" -> config.villageSeed;
++ case "desert_pyramids" -> config.desertSeed;
++ case "igloos" -> config.iglooSeed;
++ case "jungle_temples" -> config.jungleSeed;
++ case "swamp_huts" -> config.swampSeed;
++ case "pillager_outposts" -> config.outpostSeed;
++ case "ocean_monuments" -> config.monumentSeed;
++ case "woodland_mansions" -> config.mansionSeed;
++ case "buried_treasures" -> config.buriedTreasureSeed;
++ case "mineshafts" -> config.mineshaftSeed == null ? 0 : config.mineshaftSeed; // mineshaft seed is set differently
++ case "ruined_portals" -> config.portalSeed;
++ case "shipwrecks" -> config.shipwreckSeed;
++ case "ocean_ruins" -> config.oceanSeed;
++ case "nether_complexes" -> config.netherSeed;
++ case "nether_fossils" -> config.fossilSeed;
++ case "end_cities" -> config.endCitySeed;
++ case "ancient_cities" -> config.ancientCitySeed;
++ case "trail_ruins" -> config.trailRuinsSeed;
++ case "trial_chambers" -> config.trialChambersSeed;
++ default -> throw new AssertionError("Missing structure set seed in SpigotWorldConfig for " + setKey);
++ };
++ if (setKey == BuiltinStructureSets.BURIED_TREASURES) {
++ final Field field = StructurePlacement.class.getDeclaredField("HIGHLY_ARBITRARY_RANDOM_SALT");
++ field.trySetAccessible();
++ assertEquals(0, set.placement().salt);
++ assertEquals(field.get(null), salt, "Mismatched default seed for " + setKey + ". Should be " + field.get(null));
++ continue;
++ }
++ assertEquals(set.placement().salt, salt, "Mismatched default seed for " + setKey + ". Should be " + set.placement().salt);
++ }
++ }
++}
diff --git a/patches/server/0651-Fix-cancelled-powdered-snow-bucket-placement.patch b/patches/server/0651-Fix-cancelled-powdered-snow-bucket-placement.patch
new file mode 100644
index 0000000000..f07f56f707
--- /dev/null
+++ b/patches/server/0651-Fix-cancelled-powdered-snow-bucket-placement.patch
@@ -0,0 +1,31 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Fri, 8 Oct 2021 13:12:58 -0700
+Subject: [PATCH] Fix cancelled powdered snow bucket placement
+
+Cancelling the placement of powdered snow from the powdered
+snow bucket didn't revert grass that became snowy because of the
+placement.
+
+diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java
+index e320264c2283c2c09910ea70606413c73f443b1f..18f84d54ec72debec652adb22067e11aa058b238 100644
+--- a/src/main/java/net/minecraft/world/item/ItemStack.java
++++ b/src/main/java/net/minecraft/world/item/ItemStack.java
+@@ -430,7 +430,7 @@ public final class ItemStack implements DataComponentHolder {
+ int oldCount = this.getCount();
+ ServerLevel world = (ServerLevel) context.getLevel();
+
+- if (!(item instanceof BucketItem || item instanceof SolidBucketItem)) { // if not bucket
++ if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement
+ world.captureBlockStates = true;
+ // special case bonemeal
+ if (item == Items.BONE_MEAL) {
+@@ -493,7 +493,7 @@ public final class ItemStack implements DataComponentHolder {
+ world.capturedBlockStates.clear();
+ if (blocks.size() > 1) {
+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockMultiPlaceEvent(world, entityhuman, enumhand, blocks, blockposition.getX(), blockposition.getY(), blockposition.getZ());
+- } else if (blocks.size() == 1) {
++ } else if (blocks.size() == 1 && item != Items.POWDER_SNOW_BUCKET) { // Paper - Fix cancelled powdered snow bucket placement
+ placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent(world, entityhuman, enumhand, blocks.get(0), blockposition.getX(), blockposition.getY(), blockposition.getZ());
+ }
+
diff --git a/patches/server/0652-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch b/patches/server/0652-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch
new file mode 100644
index 0000000000..d5922672e4
--- /dev/null
+++ b/patches/server/0652-Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch
@@ -0,0 +1,20 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Sat, 12 Feb 2022 12:40:50 -0700
+Subject: [PATCH] Add missing Validate calls to CraftServer#getSpawnLimit
+
+Copies appropriate checks from CraftWorld#getSpawnLimit
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 3b01907bc119853e0676e912e9a29b05d9aa5763..188e3066d6659b5e45cec0b50dcbd5d20659830a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -2342,6 +2342,8 @@ public final class CraftServer implements Server {
+ @Override
+ public int getSpawnLimit(SpawnCategory spawnCategory) {
+ // Paper start - Add mobcaps commands
++ Preconditions.checkArgument(spawnCategory != null, "SpawnCategory cannot be null");
++ Preconditions.checkArgument(CraftSpawnCategory.isValidForLimits(spawnCategory), "SpawnCategory." + spawnCategory + " does not have a spawn limit.");
+ return this.getSpawnLimitUnsafe(spawnCategory);
+ }
+ public int getSpawnLimitUnsafe(final SpawnCategory spawnCategory) {
diff --git a/patches/server/0653-Add-GameEvent-tags.patch b/patches/server/0653-Add-GameEvent-tags.patch
new file mode 100644
index 0000000000..bb674d2f17
--- /dev/null
+++ b/patches/server/0653-Add-GameEvent-tags.patch
@@ -0,0 +1,81 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 3 Jan 2021 20:03:35 -0800
+Subject: [PATCH] Add GameEvent tags
+
+
+diff --git a/src/main/java/io/papermc/paper/CraftGameEventTag.java b/src/main/java/io/papermc/paper/CraftGameEventTag.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..874c420e60b6be09c806d64f40cf63663ffddc07
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/CraftGameEventTag.java
+@@ -0,0 +1,35 @@
++package io.papermc.paper;
++
++import net.minecraft.core.registries.BuiltInRegistries;
++import net.minecraft.core.registries.Registries;
++import net.minecraft.resources.ResourceKey;
++import net.minecraft.tags.TagKey;
++import org.bukkit.GameEvent;
++import org.bukkit.craftbukkit.tag.CraftTag;
++import org.bukkit.craftbukkit.util.CraftNamespacedKey;
++import org.jetbrains.annotations.NotNull;
++
++import java.util.Collections;
++import java.util.IdentityHashMap;
++import java.util.Map;
++import java.util.Objects;
++import java.util.Set;
++import java.util.stream.Collectors;
++
++public class CraftGameEventTag extends CraftTag<net.minecraft.world.level.gameevent.GameEvent, GameEvent> {
++
++ public CraftGameEventTag(net.minecraft.core.Registry<net.minecraft.world.level.gameevent.GameEvent> registry, TagKey<net.minecraft.world.level.gameevent.GameEvent> tag) {
++ super(registry, tag);
++ }
++
++ private static final Map<GameEvent, ResourceKey<net.minecraft.world.level.gameevent.GameEvent>> KEY_CACHE = Collections.synchronizedMap(new IdentityHashMap<>());
++ @Override
++ public boolean isTagged(@NotNull GameEvent gameEvent) {
++ return registry.getOrThrow(KEY_CACHE.computeIfAbsent(gameEvent, event -> ResourceKey.create(Registries.GAME_EVENT, CraftNamespacedKey.toMinecraft(event.getKey())))).is(tag);
++ }
++
++ @Override
++ public @NotNull Set<GameEvent> getValues() {
++ return getHandle().stream().map((nms) -> Objects.requireNonNull(GameEvent.getByKey(CraftNamespacedKey.fromMinecraft(BuiltInRegistries.GAME_EVENT.getKey(nms.value()))), nms + " is not a recognized game event")).collect(Collectors.toUnmodifiableSet());
++ }
++}
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 188e3066d6659b5e45cec0b50dcbd5d20659830a..72f8d3a89396f9289f5b451b24cc181e7ac3222e 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -2709,6 +2709,15 @@ public final class CraftServer implements Server {
+ return (org.bukkit.Tag<T>) new CraftDamageTag(damageRegistry, damageTagKey);
+ }
+ }
++ // Paper start
++ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> {
++ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class, "Game Event namespace must have GameEvent type");
++ TagKey<net.minecraft.world.level.gameevent.GameEvent> gameEventTagKey = TagKey.create(net.minecraft.core.registries.Registries.GAME_EVENT, key);
++ if (net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT.get(gameEventTagKey).isPresent()) {
++ return (org.bukkit.Tag<T>) new io.papermc.paper.CraftGameEventTag(net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT, gameEventTagKey);
++ }
++ }
++ // Paper end
+ default -> throw new IllegalArgumentException();
+ }
+
+@@ -2746,6 +2755,13 @@ public final class CraftServer implements Server {
+ net.minecraft.core.Registry<DamageType> damageTags = CraftRegistry.getMinecraftRegistry(Registries.DAMAGE_TYPE);
+ return damageTags.getTags().map(pair -> (org.bukkit.Tag<T>) new CraftDamageTag(damageTags, pair.key())).collect(ImmutableList.toImmutableList());
+ }
++ // Paper start
++ case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> {
++ Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class);
++ net.minecraft.core.Registry<net.minecraft.world.level.gameevent.GameEvent> gameEvents = net.minecraft.core.registries.BuiltInRegistries.GAME_EVENT;
++ return gameEvents.getTags().map(pair -> (org.bukkit.Tag<T>) new io.papermc.paper.CraftGameEventTag(gameEvents, pair.key())).collect(ImmutableList.toImmutableList());
++ }
++ // Paper end
+ default -> throw new IllegalArgumentException();
+ }
+ }
diff --git a/patches/server/0654-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch b/patches/server/0654-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch
new file mode 100644
index 0000000000..5929e0f042
--- /dev/null
+++ b/patches/server/0654-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch
@@ -0,0 +1,37 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Tue, 28 Dec 2021 07:19:01 -0800
+Subject: [PATCH] Execute chunk tasks fairly for worlds while waiting for next
+ tick
+
+Currently, only the first world would have had tasks executed.
+This might result in chunks loading far slower in the nether,
+for example.
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index c133a646baf88e0489d358e302d67f21f76b47c3..17700ebf508f3ac7a4d1cdd8d52355afaf5d006f 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -1404,6 +1404,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ if (super.pollTask()) {
+ return true;
+ } else {
++ boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
+ if (this.tickRateManager.isSprinting() || this.haveTime()) {
+ Iterator iterator = this.getAllLevels().iterator();
+
+@@ -1411,12 +1412,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ ServerLevel worldserver = (ServerLevel) iterator.next();
+
+ if (worldserver.getChunkSource().pollTask()) {
+- return true;
++ ret = true; // Paper - force execution of all worlds, do not just bias the first
+ }
+ }
+ }
+
+- return false;
++ return ret; // Paper - force execution of all worlds, do not just bias the first
+ }
+ }
+
diff --git a/patches/server/0655-Furnace-RecipesUsed-API.patch b/patches/server/0655-Furnace-RecipesUsed-API.patch
new file mode 100644
index 0000000000..f2b8d8fe65
--- /dev/null
+++ b/patches/server/0655-Furnace-RecipesUsed-API.patch
@@ -0,0 +1,48 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 13 Jan 2022 15:20:47 -0800
+Subject: [PATCH] Furnace RecipesUsed API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java
+index fd2f28ee785283bcc979c7a146308827f2ce2ba9..91c10f7d2044d60ba756179d93c5c72d32bba074 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftFurnace.java
+@@ -103,5 +103,37 @@ public abstract class CraftFurnace<T extends AbstractFurnaceBlockEntity> extends
+ snapshot.cookSpeedMultiplier = multiplier;
+ snapshot.cookingTotalTime = AbstractFurnaceBlockEntity.getTotalCookTime(this.isPlaced() ? this.world.getHandle() : null, snapshot, snapshot.recipeType, snapshot.cookSpeedMultiplier); // Update the snapshot's current total cook time to scale with the newly set multiplier
+ }
++
++ @Override
++ public int getRecipeUsedCount(org.bukkit.NamespacedKey furnaceRecipe) {
++ return this.getSnapshot().recipesUsed.getInt(io.papermc.paper.util.MCUtil.toResourceKey(net.minecraft.core.registries.Registries.RECIPE, furnaceRecipe));
++ }
++
++ @Override
++ public boolean hasRecipeUsedCount(org.bukkit.NamespacedKey furnaceRecipe) {
++ return this.getSnapshot().recipesUsed.containsKey(io.papermc.paper.util.MCUtil.toResourceKey(net.minecraft.core.registries.Registries.RECIPE, furnaceRecipe));
++ }
++
++ @Override
++ public void setRecipeUsedCount(org.bukkit.inventory.CookingRecipe<?> furnaceRecipe, int count) {
++ final var location = io.papermc.paper.util.MCUtil.toResourceKey(net.minecraft.core.registries.Registries.RECIPE, furnaceRecipe.getKey());
++ java.util.Optional<net.minecraft.world.item.crafting.RecipeHolder<?>> nmsRecipe = (this.isPlaced() ? this.world.getHandle().recipeAccess() : net.minecraft.server.MinecraftServer.getServer().getRecipeManager()).byKey(location);
++ com.google.common.base.Preconditions.checkArgument(nmsRecipe.isPresent() && nmsRecipe.get().value() instanceof net.minecraft.world.item.crafting.AbstractCookingRecipe, furnaceRecipe.getKey() + " is not recognized as a valid and registered furnace recipe");
++ if (count > 0) {
++ this.getSnapshot().recipesUsed.put(location, count);
++ } else {
++ this.getSnapshot().recipesUsed.removeInt(location);
++ }
++ }
++
++ @Override
++ public void setRecipesUsed(java.util.Map<org.bukkit.inventory.CookingRecipe<?>, Integer> recipesUsed) {
++ this.getSnapshot().recipesUsed.clear();
++ recipesUsed.forEach((recipe, integer) -> {
++ if (integer != null) {
++ this.setRecipeUsedCount(recipe, integer);
++ }
++ });
++ }
+ // Paper end
+ }
diff --git a/patches/server/0656-Configurable-sculk-sensor-listener-range.patch b/patches/server/0656-Configurable-sculk-sensor-listener-range.patch
new file mode 100644
index 0000000000..3dd1324354
--- /dev/null
+++ b/patches/server/0656-Configurable-sculk-sensor-listener-range.patch
@@ -0,0 +1,106 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 19 Aug 2021 18:45:42 -0700
+Subject: [PATCH] Configurable sculk sensor listener range
+
+== AT ==
+public-f net.minecraft.world.level.gameevent.vibrations.VibrationListener listenerRange
+
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java
+index 41ccbee5fc7767a7d5e1cdca0ec7d9a17ee80a90..1d28f117965da22694b12018923a5f1347905085 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/CalibratedSculkSensorBlockEntity.java
+@@ -20,6 +20,12 @@ public class CalibratedSculkSensorBlockEntity extends SculkSensorBlockEntity {
+ public VibrationSystem.User createVibrationUser() {
+ return new CalibratedSculkSensorBlockEntity.VibrationUser(this.getBlockPos());
+ }
++ // Paper start - Configurable sculk sensor listener range
++ @Override
++ protected void saveRangeOverride(final net.minecraft.nbt.CompoundTag nbt) {
++ if (this.rangeOverride != null && this.rangeOverride != 16) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default
++ }
++ // Paper end - Configurable sculk sensor listener range
+
+ protected class VibrationUser extends SculkSensorBlockEntity.VibrationUser {
+ public VibrationUser(final BlockPos pos) {
+@@ -28,6 +34,7 @@ public class CalibratedSculkSensorBlockEntity extends SculkSensorBlockEntity {
+
+ @Override
+ public int getListenerRadius() {
++ if (CalibratedSculkSensorBlockEntity.this.rangeOverride != null) return CalibratedSculkSensorBlockEntity.this.rangeOverride; // Paper - Configurable sculk sensor listener range
+ return 16;
+ }
+
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java
+index 070814eb011ab8e02218c91e1cf75be5501c1a0a..28849cf84afcdc0d9fc245fac1a8d769a2db3b68 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java
+@@ -26,6 +26,7 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList
+ private final VibrationSystem.Listener vibrationListener;
+ private final VibrationSystem.User vibrationUser = this.createVibrationUser();
+ public int lastVibrationFrequency;
++ @Nullable public Integer rangeOverride = null; // Paper - Configurable sculk sensor listener range
+
+ protected SculkSensorBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
+ super(type, pos, state);
+@@ -52,8 +53,16 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList
+ .resultOrPartial(string -> LOGGER.error("Failed to parse vibration listener for Sculk Sensor: '{}'", string))
+ .ifPresent(listener -> this.vibrationData = listener);
+ }
++ // Paper start - Configurable sculk sensor listener range
++ if (nbt.contains(PAPER_LISTENER_RANGE_NBT_KEY)) {
++ this.rangeOverride = nbt.getInt(PAPER_LISTENER_RANGE_NBT_KEY);
++ } else {
++ this.rangeOverride = null;
++ }
++ // Paper end - Configurable sculk sensor listener range
+ }
+
++ protected static final String PAPER_LISTENER_RANGE_NBT_KEY = "Paper.ListenerRange"; // Paper - Configurable sculk sensor listener range
+ @Override
+ protected void saveAdditional(CompoundTag nbt, HolderLookup.Provider registries) {
+ super.saveAdditional(nbt, registries);
+@@ -63,7 +72,13 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList
+ .encodeStart(registryOps, this.vibrationData)
+ .resultOrPartial(string -> LOGGER.error("Failed to encode vibration listener for Sculk Sensor: '{}'", string))
+ .ifPresent(listenerNbt -> nbt.put("listener", listenerNbt));
++ this.saveRangeOverride(nbt); // Paper - Configurable sculk sensor listener range
++ }
++ // Paper start - Configurable sculk sensor listener range
++ protected void saveRangeOverride(CompoundTag nbt) {
++ if (this.rangeOverride != null && this.rangeOverride != VibrationUser.LISTENER_RANGE) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.rangeOverride); // only save if it's different from the default
+ }
++ // Paper end - Configurable sculk sensor listener range
+
+ @Override
+ public VibrationSystem.Data getVibrationData() {
+@@ -100,6 +115,7 @@ public class SculkSensorBlockEntity extends BlockEntity implements GameEventList
+
+ @Override
+ public int getListenerRadius() {
++ if (SculkSensorBlockEntity.this.rangeOverride != null) return SculkSensorBlockEntity.this.rangeOverride; // Paper - Configurable sculk sensor listener range
+ return 8;
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java
+index 70d85dbfcaae7ee632a4f541334302b46615a254..6ba229afb0219ff229fd794c59d7585f010742ee 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSculkSensor.java
+@@ -36,4 +36,17 @@ public class CraftSculkSensor<T extends SculkSensorBlockEntity> extends CraftBlo
+ public CraftSculkSensor<T> copy(Location location) {
+ return new CraftSculkSensor<>(this, location);
+ }
++
++ // Paper start
++ @Override
++ public int getListenerRange() {
++ return this.getSnapshot().getListener().getListenerRadius();
++ }
++
++ @Override
++ public void setListenerRange(int range) {
++ Preconditions.checkArgument(range > 0, "Vibration listener range must be greater than 0");
++ this.getSnapshot().rangeOverride = range;
++ }
++ // Paper end
+ }
diff --git a/patches/server/0657-Add-missing-block-data-API.patch b/patches/server/0657-Add-missing-block-data-API.patch
new file mode 100644
index 0000000000..7c18d0cb0d
--- /dev/null
+++ b/patches/server/0657-Add-missing-block-data-API.patch
@@ -0,0 +1,214 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 16 Oct 2021 22:57:31 -0700
+Subject: [PATCH] Add missing block data API
+
+General purpose patch adding missing getters/setters to BlockData and
+its child types.
+
+Co-authored-by: SoSeDiK <[email protected]>
+Co-authored-by: Fabrizio La Rosa <[email protected]>
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftBed.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftBed.java
+index 2ccf3fbe3f991b7a014cff3bcd424e6a81bc310a..e5450d3511389bf3bd6461fb6ec65ea82e4ae9f0 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftBed.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftBed.java
+@@ -51,4 +51,11 @@ public final class CraftBed extends org.bukkit.craftbukkit.block.data.CraftBlock
+ public java.util.Set<org.bukkit.block.BlockFace> getFaces() {
+ return this.getValues(CraftBed.FACING, org.bukkit.block.BlockFace.class);
+ }
++
++ // Paper start
++ @Override
++ public void setOccupied(boolean occupied) {
++ set(CraftBed.OCCUPIED, occupied);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java
+index 2230160d5e04e979467a56346600436c1e5dd70c..08436bfeba2f35fb11b16c4f71f76e13c0d44b1a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java
+@@ -31,6 +31,12 @@ public final class CraftCandle extends org.bukkit.craftbukkit.block.data.CraftBl
+ public int getMaximumCandles() {
+ return getMax(CraftCandle.CANDLES);
+ }
++ // Paper start
++ @Override
++ public int getMinimumCandles() {
++ return getMin(CraftCandle.CANDLES);
++ }
++ // Paper end
+
+ // org.bukkit.craftbukkit.block.data.CraftLightable
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java
+index 7ce2e8b733bcd496dcfccb1ddfcb7c5c1b64052e..5ae27fc8f9d18bae949d335ea53e7e70917f0e80 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java
+@@ -31,4 +31,11 @@ public final class CraftComposter extends org.bukkit.craftbukkit.block.data.Craf
+ public int getMaximumLevel() {
+ return getMax(CraftComposter.LEVEL);
+ }
++
++ // Paper start
++ @Override
++ public int getMinimumLevel() {
++ return getMin(CraftComposter.LEVEL);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftDecoratedPot.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftDecoratedPot.java
+index 356230b9b266974e36d0508f8c239714d673504d..b7ea9a6fba6b4fc157dfcc4bee099871b8ad7380 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftDecoratedPot.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftDecoratedPot.java
+@@ -45,4 +45,18 @@ public final class CraftDecoratedPot extends org.bukkit.craftbukkit.block.data.C
+ public void setWaterlogged(boolean waterlogged) {
+ this.set(CraftDecoratedPot.WATERLOGGED, waterlogged);
+ }
++
++ // Paper start - add missing block data api
++ private static final net.minecraft.world.level.block.state.properties.BooleanProperty CRACKED = getBoolean(net.minecraft.world.level.block.DecoratedPotBlock.class, "cracked");
++
++ @Override
++ public boolean isCracked() {
++ return this.get(CraftDecoratedPot.CRACKED);
++ }
++
++ @Override
++ public void setCracked(final boolean cracked) {
++ this.set(CraftDecoratedPot.CRACKED, cracked);
++ }
++ // Paper end - add missing block data api
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java
+index 70d734fc71a4499bbf569b3908aa5fbbdf19e6a0..1af5fe48c5861077555e6bdeb6312859b7b37eb2 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftFluids.java
+@@ -31,4 +31,11 @@ public final class CraftFluids extends org.bukkit.craftbukkit.block.data.CraftBl
+ public int getMaximumLevel() {
+ return getMax(CraftFluids.LEVEL);
+ }
++
++ // Paper start
++ @Override
++ public int getMinimumLevel() {
++ return getMin(CraftFluids.LEVEL);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java
+index bf0d53f65f8a672c385b2e798b109a9662725f9e..c0e0cbceb0b5c36f4ac4672f217027a5898900a6 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLayeredCauldron.java
+@@ -31,4 +31,11 @@ public final class CraftLayeredCauldron extends org.bukkit.craftbukkit.block.dat
+ public int getMaximumLevel() {
+ return getMax(CraftLayeredCauldron.LEVEL);
+ }
++
++ // Paper start
++ @Override
++ public int getMinimumLevel() {
++ return getMin(CraftLayeredCauldron.LEVEL);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java
+index 33d9a950ed678595fe2573e9f89a8d1716040503..ab336b400c1937ff86b681b27b1550e4b7f1ab79 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java
+@@ -51,4 +51,16 @@ public final class CraftLeaves extends org.bukkit.craftbukkit.block.data.CraftBl
+ public void setWaterlogged(boolean waterlogged) {
+ this.set(CraftLeaves.WATERLOGGED, waterlogged);
+ }
++
++ // Paper start
++ @Override
++ public int getMaximumDistance() {
++ return getMax(CraftLeaves.DISTANCE);
++ }
++
++ @Override
++ public int getMinimumDistance() {
++ return getMin(CraftLeaves.DISTANCE);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java
+index 49f314b1447212a1a5a7623d2302b5960a44ce6e..8c936a95effa84ba0337d2aaf880cc561591fb33 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLight.java
+@@ -32,6 +32,13 @@ public final class CraftLight extends org.bukkit.craftbukkit.block.data.CraftBlo
+ return getMax(CraftLight.LEVEL);
+ }
+
++ // Paper start
++ @Override
++ public int getMinimumLevel() {
++ return getMin(CraftLight.LEVEL);
++ }
++ // Paper end
++
+ // org.bukkit.craftbukkit.block.data.CraftWaterlogged
+
+ private static final net.minecraft.world.level.block.state.properties.BooleanProperty WATERLOGGED = getBoolean(net.minecraft.world.level.block.LightBlock.class, "waterlogged");
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java
+index 7a1f2fd2f7f8f1b46352fe2c4d0cdf23a88020fd..8b621aaeadcf2cc6e2ccdbab92f4ae2b89a6ca08 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftMangroveLeaves.java
+@@ -51,4 +51,16 @@ public final class CraftMangroveLeaves extends org.bukkit.craftbukkit.block.data
+ public void setWaterlogged(boolean waterlogged) {
+ this.set(CraftMangroveLeaves.WATERLOGGED, waterlogged);
+ }
++
++ // Paper start
++ @Override
++ public int getMinimumDistance() {
++ return getMin(CraftMangroveLeaves.DISTANCE);
++ }
++
++ @Override
++ public int getMaximumDistance() {
++ return getMax(CraftMangroveLeaves.DISTANCE);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftParticleLeaves.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftParticleLeaves.java
+index db4849a35d34da4ab42bbe7569c4944ed95d8f2b..e37e84c333a42006b6c32cf5cd71c0bbfa725141 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftParticleLeaves.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftParticleLeaves.java
+@@ -51,4 +51,16 @@ public final class CraftParticleLeaves extends org.bukkit.craftbukkit.block.data
+ public void setWaterlogged(boolean waterlogged) {
+ this.set(CraftParticleLeaves.WATERLOGGED, waterlogged);
+ }
++
++ // Paper start
++ @Override
++ public int getMaximumDistance() {
++ return getMax(DISTANCE);
++ }
++
++ @Override
++ public int getMinimumDistance() {
++ return getMin(DISTANCE);
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPinkPetals.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPinkPetals.java
+index 78b220a6f460cd91ad1574c0d32f3e4288eaf431..0f7df1b4c58ba731832958043ba345ec77737e54 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPinkPetals.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftPinkPetals.java
+@@ -27,6 +27,13 @@ public final class CraftPinkPetals extends org.bukkit.craftbukkit.block.data.Cra
+ this.set(CraftPinkPetals.FLOWER_AMOUNT, flower_amount);
+ }
+
++ // Paper start
++ @Override
++ public int getMinimumFlowerAmount() {
++ return getMin(CraftPinkPetals.FLOWER_AMOUNT);
++ }
++ // Paper end
++
+ @Override
+ public int getMaximumFlowerAmount() {
+ return getMax(CraftPinkPetals.FLOWER_AMOUNT);
diff --git a/patches/server/0658-Option-to-have-default-CustomSpawners-in-custom-worl.patch b/patches/server/0658-Option-to-have-default-CustomSpawners-in-custom-worl.patch
new file mode 100644
index 0000000000..69da29074a
--- /dev/null
+++ b/patches/server/0658-Option-to-have-default-CustomSpawners-in-custom-worl.patch
@@ -0,0 +1,32 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 19 Feb 2022 20:15:41 -0800
+Subject: [PATCH] Option to have default CustomSpawners in custom worlds
+
+By default, only LevelStem's that specifically match the ResourceKey for
+OVERWORLD will have the 5 (currently) impls of CustomSpawner (for
+phantoms, wandering traders, etc.). This adds an option to instead of
+just looking at the LevelStem key, look at the DimensionType key which
+is one level below that. Defaults to off to keep vanilla behavior.
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 17700ebf508f3ac7a4d1cdd8d52355afaf5d006f..64dce8d94cf261113d6f8b99020bf710b9762c2b 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -645,7 +645,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ this.commandStorage = new CommandStorage(worldpersistentdata);
+ } else {
+ ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(this.worldData.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS));
+- world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, ImmutableList.of(), true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
++ // Paper start - option to use the dimension_type to check if spawners should be added. I imagine mojang will add some datapack-y way of managing this in the future.
++ final List<CustomSpawner> spawners;
++ if (io.papermc.paper.configuration.GlobalConfiguration.get().misc.useDimensionTypeForCustomSpawners && this.registryAccess().lookupOrThrow(Registries.DIMENSION_TYPE).getResourceKey(worlddimension.type().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) {
++ spawners = list;
++ } else {
++ spawners = Collections.emptyList();
++ }
++ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, this.overworld().getRandomSequences(), org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider);
++ // Paper end - option to use the dimension_type to check if spawners should be added
+ }
+
+ worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
diff --git a/patches/server/0659-Put-world-into-worldlist-before-initing-the-world.patch b/patches/server/0659-Put-world-into-worldlist-before-initing-the-world.patch
new file mode 100644
index 0000000000..ffe26b0b55
--- /dev/null
+++ b/patches/server/0659-Put-world-into-worldlist-before-initing-the-world.patch
@@ -0,0 +1,41 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Tue, 22 Feb 2022 14:21:35 -0800
+Subject: [PATCH] Put world into worldlist before initing the world
+
+Some parts of legacy conversion will need the overworld
+to get the legacy structure data storage
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 64dce8d94cf261113d6f8b99020bf710b9762c2b..03ee72a20330dd9ba6ff2808c1252454a6c217bc 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -657,9 +657,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ }
+
+ worlddata.setModdedInfo(this.getServerModName(), this.getModdedStatus().shouldReportAsModified());
++ this.addLevel(world); // Paper - Put world into worldlist before initing the world; move up
+ this.initWorld(world, worlddata, this.worldData, worldoptions);
+
+- this.addLevel(world);
++ // Paper - Put world into worldlist before initing the world; move up
+ this.getPlayerList().addWorldborderListener(world);
+
+ if (worlddata.getCustomBossEvents() != null) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 72f8d3a89396f9289f5b451b24cc181e7ac3222e..75bd9617164c63a641602bf772a5e0f15a322c7e 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -1336,10 +1336,11 @@ public final class CraftServer implements Server {
+ return null;
+ }
+
++ this.console.addLevel(internal); // Paper - Put world into worldlist before initing the world; move up
+ this.console.initWorld(internal, worlddata, worlddata, worlddata.worldGenOptions());
+
+ internal.setSpawnSettings(true);
+- this.console.addLevel(internal);
++ // Paper - Put world into worldlist before initing the world; move up
+
+ this.getServer().prepareLevels(internal.getChunkSource().chunkMap.progressListener, internal);
+ internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
diff --git a/patches/server/0660-Custom-Potion-Mixes.patch b/patches/server/0660-Custom-Potion-Mixes.patch
new file mode 100644
index 0000000000..162f383a75
--- /dev/null
+++ b/patches/server/0660-Custom-Potion-Mixes.patch
@@ -0,0 +1,329 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 7 Oct 2021 14:34:55 -0700
+Subject: [PATCH] Custom Potion Mixes
+
+== AT ==
+public-f net.minecraft.server.MinecraftServer potionBrewing
+
+diff --git a/src/main/java/io/papermc/paper/potion/PaperPotionBrewer.java b/src/main/java/io/papermc/paper/potion/PaperPotionBrewer.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..d9390227a2bba4e03aa9ee592ca157127633c41b
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/potion/PaperPotionBrewer.java
+@@ -0,0 +1,56 @@
++package io.papermc.paper.potion;
++
++import com.google.common.base.Preconditions;
++import java.util.Collection;
++import net.minecraft.server.MinecraftServer;
++import org.bukkit.NamespacedKey;
++import org.bukkit.potion.PotionBrewer;
++import org.bukkit.potion.PotionEffect;
++import org.bukkit.potion.PotionType;
++import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.framework.qual.DefaultQualifier;
++
++@DefaultQualifier(NonNull.class)
++public class PaperPotionBrewer implements PotionBrewer {
++
++ private final MinecraftServer minecraftServer;
++
++ public PaperPotionBrewer(final MinecraftServer minecraftServer) {
++ this.minecraftServer = minecraftServer;
++ }
++
++ @Override
++ @Deprecated(forRemoval = true)
++ public Collection<PotionEffect> getEffects(PotionType type, boolean upgraded, boolean extended) {
++ final org.bukkit.NamespacedKey key = type.getKey();
++
++ Preconditions.checkArgument(!key.getKey().startsWith("strong_"), "Strong potion type cannot be used directly, got %s", key);
++ Preconditions.checkArgument(!key.getKey().startsWith("long_"), "Extended potion type cannot be used directly, got %s", key);
++
++ org.bukkit.NamespacedKey effectiveKey = key;
++ if (upgraded) {
++ effectiveKey = new org.bukkit.NamespacedKey(key.namespace(), "strong_" + key.key());
++ } else if (extended) {
++ effectiveKey = new org.bukkit.NamespacedKey(key.namespace(), "long_" + key.key());
++ }
++
++ final org.bukkit.potion.PotionType effectivePotionType = org.bukkit.Registry.POTION.get(effectiveKey);
++ Preconditions.checkNotNull(type, "Unknown potion type from data " + effectiveKey.asMinimalString()); // Legacy error message in 1.20.4
++ return effectivePotionType.getPotionEffects();
++ }
++
++ @Override
++ public void addPotionMix(final PotionMix potionMix) {
++ this.minecraftServer.potionBrewing().addPotionMix(potionMix);
++ }
++
++ @Override
++ public void removePotionMix(final NamespacedKey key) {
++ this.minecraftServer.potionBrewing.removePotionMix(key);
++ }
++
++ @Override
++ public void resetPotionMixes() {
++ this.minecraftServer.potionBrewing = this.minecraftServer.potionBrewing().reload(this.minecraftServer.getWorldData().enabledFeatures());
++ }
++}
+diff --git a/src/main/java/io/papermc/paper/potion/PaperPotionMix.java b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java
+new file mode 100644
+index 0000000000000000000000000000000000000000..7ea357ac2f3a93db4ebdf24b5072be7d1cad3e33
+--- /dev/null
++++ b/src/main/java/io/papermc/paper/potion/PaperPotionMix.java
+@@ -0,0 +1,21 @@
++package io.papermc.paper.potion;
++
++import java.util.function.Predicate;
++import net.minecraft.world.item.ItemStack;
++import org.bukkit.craftbukkit.inventory.CraftItemStack;
++import org.bukkit.craftbukkit.inventory.CraftRecipe;
++import org.bukkit.inventory.RecipeChoice;
++
++public record PaperPotionMix(ItemStack result, Predicate<ItemStack> input, Predicate<ItemStack> ingredient) {
++
++ public PaperPotionMix(PotionMix potionMix) {
++ this(CraftItemStack.asNMSCopy(potionMix.getResult()), convert(potionMix.getInput()), convert(potionMix.getIngredient()));
++ }
++
++ static Predicate<ItemStack> convert(final RecipeChoice choice) {
++ if (choice instanceof PredicateRecipeChoice predicateRecipeChoice) {
++ return stack -> predicateRecipeChoice.test(CraftItemStack.asBukkitCopy(stack));
++ }
++ return CraftRecipe.toIngredient(choice, true);
++ }
++}
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index 03ee72a20330dd9ba6ff2808c1252454a6c217bc..fcc00d5b0be3d2adb92c8243ccca8d0190fcc413 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -2196,6 +2196,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
+ this.worldData.setDataConfiguration(worlddataconfiguration);
+ this.resources.managers.updateStaticRegistryTags();
+ this.resources.managers.getRecipeManager().finalizeRecipeLoading(this.worldData.enabledFeatures());
++ this.potionBrewing = this.potionBrewing.reload(this.worldData.enabledFeatures()); // Paper - Custom Potion Mixes
+ this.getPlayerList().saveAll();
+ this.getPlayerList().reloadResources();
+ this.functionManager.replaceLibrary(this.resources.managers.getFunctionLibrary());
+diff --git a/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java b/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java
+index 6acc06ab49549ebd80e9871dce7f3fe6ebde9498..d2688ca4430e2e45b35b97b0b3b7c79b5aac23a4 100644
+--- a/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java
++++ b/src/main/java/net/minecraft/world/inventory/BrewingStandMenu.java
+@@ -57,9 +57,11 @@ public class BrewingStandMenu extends AbstractContainerMenu {
+ this.brewingStandData = propertyDelegate;
+ PotionBrewing potionbrewer = playerInventory.player.level().potionBrewing();
+
+- this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 0, 56, 51));
+- this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 1, 79, 58));
+- this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 2, 102, 51));
++ // Paper start - custom potion mixes
++ this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 0, 56, 51, potionbrewer));
++ this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 1, 79, 58, potionbrewer));
++ this.addSlot(new BrewingStandMenu.PotionSlot(inventory, 2, 102, 51, potionbrewer));
++ // Paper end - custom potion mixes
+ this.ingredientSlot = this.addSlot(new BrewingStandMenu.IngredientsSlot(potionbrewer, inventory, 3, 79, 17));
+ this.addSlot(new BrewingStandMenu.FuelSlot(inventory, 4, 17, 17));
+ this.addDataSlots(propertyDelegate);
+@@ -90,7 +92,7 @@ public class BrewingStandMenu extends AbstractContainerMenu {
+ if (!this.moveItemStackTo(itemstack1, 3, 4, false)) {
+ return ItemStack.EMPTY;
+ }
+- } else if (BrewingStandMenu.PotionSlot.mayPlaceItem(itemstack)) {
++ } else if (BrewingStandMenu.PotionSlot.mayPlaceItem(itemstack, this.player.player.level().potionBrewing())) { // Paper - custom potion mixes
+ if (!this.moveItemStackTo(itemstack1, 0, 3, false)) {
+ return ItemStack.EMPTY;
+ }
+@@ -139,13 +141,15 @@ public class BrewingStandMenu extends AbstractContainerMenu {
+
+ private static class PotionSlot extends Slot {
+
+- public PotionSlot(Container inventory, int index, int x, int y) {
++ private final PotionBrewing potionBrewing; // Paper - custom potion mixes
++ public PotionSlot(Container inventory, int index, int x, int y, PotionBrewing potionBrewing) { // Paper - custom potion mixes
+ super(inventory, index, x, y);
++ this.potionBrewing = potionBrewing; // Paper - custom potion mixes
+ }
+
+ @Override
+ public boolean mayPlace(ItemStack stack) {
+- return PotionSlot.mayPlaceItem(stack);
++ return PotionSlot.mayPlaceItem(stack, this.potionBrewing); // Paper - custom potion mixes
+ }
+
+ @Override
+@@ -164,8 +168,8 @@ public class BrewingStandMenu extends AbstractContainerMenu {
+ super.onTake(player, stack);
+ }
+
+- public static boolean mayPlaceItem(ItemStack stack) {
+- return stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE);
++ public static boolean mayPlaceItem(ItemStack stack, PotionBrewing potionBrewing) { // Paper - custom potion mixes
++ return stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionBrewing.isCustomInput(stack); // Paper - Custom Potion Mixes
+ }
+
+ @Override
+diff --git a/src/main/java/net/minecraft/world/item/alchemy/PotionBrewing.java b/src/main/java/net/minecraft/world/item/alchemy/PotionBrewing.java
+index 50e81e3babd331077eda8daa769eb2b3f99e8ca2..ca01f3344005b295c7ae98f6d5b03f79513b12a4 100644
+--- a/src/main/java/net/minecraft/world/item/alchemy/PotionBrewing.java
++++ b/src/main/java/net/minecraft/world/item/alchemy/PotionBrewing.java
+@@ -19,6 +19,7 @@ public class PotionBrewing {
+ private final List<Ingredient> containers;
+ private final List<PotionBrewing.Mix<Potion>> potionMixes;
+ private final List<PotionBrewing.Mix<Item>> containerMixes;
++ private final it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<org.bukkit.NamespacedKey, io.papermc.paper.potion.PaperPotionMix> customMixes = new it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap<>(); // Paper - Custom Potion Mixes
+
+ PotionBrewing(List<Ingredient> potionTypes, List<PotionBrewing.Mix<Potion>> potionRecipes, List<PotionBrewing.Mix<Item>> itemRecipes) {
+ this.containers = potionTypes;
+@@ -27,7 +28,7 @@ public class PotionBrewing {
+ }
+
+ public boolean isIngredient(ItemStack stack) {
+- return this.isContainerIngredient(stack) || this.isPotionIngredient(stack);
++ return this.isContainerIngredient(stack) || this.isPotionIngredient(stack) || this.isCustomIngredient(stack); // Paper - Custom Potion Mixes
+ }
+
+ private boolean isContainer(ItemStack stack) {
+@@ -71,6 +72,11 @@ public class PotionBrewing {
+ }
+
+ public boolean hasMix(ItemStack input, ItemStack ingredient) {
++ // Paper start - Custom Potion Mixes
++ if (this.hasCustomMix(input, ingredient)) {
++ return true;
++ }
++ // Paper end - Custom Potion Mixes
+ return this.isContainer(input) && (this.hasContainerMix(input, ingredient) || this.hasPotionMix(input, ingredient));
+ }
+
+@@ -103,6 +109,13 @@ public class PotionBrewing {
+ if (input.isEmpty()) {
+ return input;
+ } else {
++ // Paper start - Custom Potion Mixes
++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) {
++ if (mix.input().test(input) && mix.ingredient().test(ingredient)) {
++ return mix.result().copy();
++ }
++ }
++ // Paper end - Custom Potion Mixes
+ Optional<Holder<Potion>> optional = input.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY).potion();
+ if (optional.isEmpty()) {
+ return input;
+@@ -190,6 +203,50 @@ public class PotionBrewing {
+ builder.addMix(Potions.SLOW_FALLING, Items.REDSTONE, Potions.LONG_SLOW_FALLING);
+ }
+
++ // Paper start - Custom Potion Mixes
++ public boolean isCustomIngredient(ItemStack stack) {
++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) {
++ if (mix.ingredient().test(stack)) {
++ return true;
++ }
++ }
++ return false;
++ }
++
++ public boolean isCustomInput(ItemStack stack) {
++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) {
++ if (mix.input().test(stack)) {
++ return true;
++ }
++ }
++ return false;
++ }
++
++ private boolean hasCustomMix(ItemStack input, ItemStack ingredient) {
++ for (io.papermc.paper.potion.PaperPotionMix mix : this.customMixes.values()) {
++ if (mix.input().test(input) && mix.ingredient().test(ingredient)) {
++ return true;
++ }
++ }
++ return false;
++ }
++
++ public void addPotionMix(io.papermc.paper.potion.PotionMix mix) {
++ if (this.customMixes.containsKey(mix.getKey())) {
++ throw new IllegalArgumentException("Duplicate recipe ignored with ID " + mix.getKey());
++ }
++ this.customMixes.putAndMoveToFirst(mix.getKey(), new io.papermc.paper.potion.PaperPotionMix(mix));
++ }
++
++ public boolean removePotionMix(org.bukkit.NamespacedKey key) {
++ return this.customMixes.remove(key) != null;
++ }
++
++ public PotionBrewing reload(FeatureFlagSet flags) {
++ return bootstrap(flags);
++ }
++ // Paper end - Custom Potion Mixes
++
+ public static class Builder {
+ private final List<Ingredient> containers = new ArrayList<>();
+ private final List<PotionBrewing.Mix<Potion>> potionMixes = new ArrayList<>();
+diff --git a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
+index e167c2834f1b7899a7d11cef782940deeb739a9c..2bafacd7bc56186d9105d2031180f8c4a6940018 100644
+--- a/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
++++ b/src/main/java/net/minecraft/world/level/block/entity/BrewingStandBlockEntity.java
+@@ -316,12 +316,12 @@ public class BrewingStandBlockEntity extends BaseContainerBlockEntity implements
+
+ @Override
+ public boolean canPlaceItem(int slot, ItemStack stack) {
++ PotionBrewing potionbrewer = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY; // Paper - move up
+ if (slot == 3) {
+- PotionBrewing potionbrewer = this.level != null ? this.level.potionBrewing() : PotionBrewing.EMPTY;
+
+ return potionbrewer.isIngredient(stack);
+ } else {
+- return slot == 4 ? stack.is(ItemTags.BREWING_FUEL) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE)) && this.getItem(slot).isEmpty();
++ return slot == 4 ? stack.is(ItemTags.BREWING_FUEL) : (stack.is(Items.POTION) || stack.is(Items.SPLASH_POTION) || stack.is(Items.LINGERING_POTION) || stack.is(Items.GLASS_BOTTLE) || potionbrewer.isCustomInput(stack)) && this.getItem(slot).isEmpty(); // Paper - Custom Potion Mixes
+ }
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 75bd9617164c63a641602bf772a5e0f15a322c7e..82e1c4713e043c4903b1f0154609da4558f90aef 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -311,6 +311,7 @@ public final class CraftServer implements Server {
+ private final io.papermc.paper.datapack.PaperDatapackManager datapackManager; // Paper
+ public static Exception excessiveVelEx; // Paper - Velocity warnings
+ private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper
++ private final io.papermc.paper.potion.PaperPotionBrewer potionBrewer; // Paper - Custom Potion Mixes
+
+ static {
+ ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
+@@ -394,6 +395,7 @@ public final class CraftServer implements Server {
+ if (this.configuration.getBoolean("settings.use-map-color-cache")) {
+ MapPalette.setMapColorCache(new CraftMapColorCache(this.logger));
+ }
++ this.potionBrewer = new io.papermc.paper.potion.PaperPotionBrewer(console); // Paper - custom potion mixes
+ datapackManager = new io.papermc.paper.datapack.PaperDatapackManager(console.getPackRepository()); // Paper
+ }
+
+@@ -3076,5 +3078,9 @@ public final class CraftServer implements Server {
+ return datapackManager;
+ }
+
++ @Override
++ public io.papermc.paper.potion.PaperPotionBrewer getPotionBrewer() {
++ return this.potionBrewer;
++ }
+ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
+index fa47232eeb36ed92b5a526bb00398f08b6c0ab41..5b176cd5f80a49e3a2afbcbf4fe2958143719bce 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftRecipe.java
+@@ -23,6 +23,11 @@ public interface CraftRecipe extends Recipe {
+ }
+
+ default Ingredient toNMS(RecipeChoice bukkit, boolean requireNotEmpty) {
++ // Paper start
++ return toIngredient(bukkit, requireNotEmpty);
++ }
++ static Ingredient toIngredient(RecipeChoice bukkit, boolean requireNotEmpty) {
++ // Paper end
+ Ingredient stack;
+
+ if (bukkit == null) {
diff --git a/patches/server/0661-Force-close-world-loading-screen.patch b/patches/server/0661-Force-close-world-loading-screen.patch
new file mode 100644
index 0000000000..820a46bebe
--- /dev/null
+++ b/patches/server/0661-Force-close-world-loading-screen.patch
@@ -0,0 +1,32 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Wed, 2 Mar 2022 09:45:56 +0100
+Subject: [PATCH] Force close world loading screen
+
+Dead players would be stuck in the world loading screen and other players may
+miss messages and similar sent in the join event if chunk loading is slow.
+Paper already circumvents falling through the world before chunks are loaded,
+so we do not need that. The client only needs the chunk it is currently in to
+be loaded to close the loading screen, so we just send an empty one.
+
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index 3a0e0196f5bfa554b23fff9ff1a18a189b36452e..c8bb797a322220647d5839cc5d20b3ac7c01b8ba 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -406,6 +406,16 @@ public abstract class PlayerList {
+ }
+ // Paper end - Configurable player collision
+ PlayerList.LOGGER.info("{}[{}] logged in with entity id {} at ([{}]{}, {}, {})", player.getName().getString(), s1, player.getId(), worldserver1.serverLevelData.getLevelName(), player.getX(), player.getY(), player.getZ());
++ // Paper start - Send empty chunk, so players aren't stuck in the world loading screen with our chunk system not sending chunks when dead
++ if (player.isDeadOrDying()) {
++ net.minecraft.core.Holder<net.minecraft.world.level.biome.Biome> plains = worldserver1.registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.BIOME)
++ .getOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS);
++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket(
++ new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains),
++ worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null)
++ );
++ }
++ // Paper end - Send empty chunk
+ }
+
+ public void updateEntireScoreboard(ServerScoreboard scoreboard, ServerPlayer player) {
diff --git a/patches/server/0662-Fix-falling-block-spawn-methods.patch b/patches/server/0662-Fix-falling-block-spawn-methods.patch
new file mode 100644
index 0000000000..9703a17436
--- /dev/null
+++ b/patches/server/0662-Fix-falling-block-spawn-methods.patch
@@ -0,0 +1,57 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Nassim Jahnke <[email protected]>
+Date: Fri, 4 Mar 2022 20:35:19 +0100
+Subject: [PATCH] Fix falling block spawn methods
+
+Restores the API behavior from previous versions of the server
+- Do not call API events
+- Do not replace the existing block in the world
+
+== AT ==
+public net.minecraft.world.entity.item.FallingBlockEntity <init>(Lnet/minecraft/world/level/Level;DDDLnet/minecraft/world/level/block/state/BlockState;)V
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index 66778ebd82563823f692c7151f40a373e8d7427a..827cf8b2e241e49dac961b103f32546a04f8a2f9 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -1386,7 +1386,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+ Preconditions.checkArgument(material != null, "Material cannot be null");
+ Preconditions.checkArgument(material.isBlock(), "Material.%s must be a block", material);
+
+- FallingBlockEntity entity = FallingBlockEntity.fall(this.world, BlockPos.containing(location.getX(), location.getY(), location.getZ()), CraftBlockType.bukkitToMinecraft(material).defaultBlockState(), SpawnReason.CUSTOM);
++ // Paper start - restore API behavior for spawning falling blocks
++ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), CraftBlockType.bukkitToMinecraft(material).defaultBlockState()); // Paper
++ entity.time = 1;
++
++ this.world.addFreshEntity(entity, SpawnReason.CUSTOM);
++ // Paper end - restore API behavior for spawning falling blocks
+ return (FallingBlock) entity.getBukkitEntity();
+ }
+
+@@ -1395,7 +1400,12 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+ Preconditions.checkArgument(location != null, "Location cannot be null");
+ Preconditions.checkArgument(data != null, "BlockData cannot be null");
+
+- FallingBlockEntity entity = FallingBlockEntity.fall(this.world, BlockPos.containing(location.getX(), location.getY(), location.getZ()), ((CraftBlockData) data).getState(), SpawnReason.CUSTOM);
++ // Paper start - restore API behavior for spawning falling blocks
++ FallingBlockEntity entity = new FallingBlockEntity(this.world, location.getX(), location.getY(), location.getZ(), ((CraftBlockData) data).getState());
++ entity.time = 1;
++
++ this.world.addFreshEntity(entity, SpawnReason.CUSTOM);
++ // Paper end - restore API behavior for spawning falling blocks
+ return (FallingBlock) entity.getBukkitEntity();
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
+index 95c2ef53e6c5574e69614d8058eb3ed94ab6b0d6..f3ad9c57ce2fc06b90b7ab7e1f31ef0e1b564c9f 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
+@@ -438,7 +438,7 @@ public final class CraftEntityTypes {
+ register(new EntityTypeData<>(EntityType.TNT, TNTPrimed.class, CraftTNTPrimed::new, spawnData -> new PrimedTnt(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), null)));
+ register(new EntityTypeData<>(EntityType.FALLING_BLOCK, FallingBlock.class, CraftFallingBlock::new, spawnData -> {
+ BlockPos pos = BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z());
+- return FallingBlockEntity.fall(spawnData.minecraftWorld(), pos, spawnData.world().getBlockState(pos));
++ return new FallingBlockEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), spawnData.world().getBlockState(pos)); // Paper - create falling block entities correctly
+ }));
+ register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), net.minecraft.world.item.ItemStack.EMPTY)));
+ register(new EntityTypeData<>(EntityType.EVOKER_FANGS, EvokerFangs.class, CraftEvokerFangs::new, spawnData -> new net.minecraft.world.entity.projectile.EvokerFangs(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), (float) Math.toRadians(spawnData.yaw()), 0, null)));
diff --git a/patches/server/0663-Expose-furnace-minecart-push-values.patch b/patches/server/0663-Expose-furnace-minecart-push-values.patch
new file mode 100644
index 0000000000..fb48aabb5f
--- /dev/null
+++ b/patches/server/0663-Expose-furnace-minecart-push-values.patch
@@ -0,0 +1,42 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: EpicKnarvik97 <[email protected]>
+Date: Sat, 5 Mar 2022 20:58:46 +0100
+Subject: [PATCH] Expose furnace minecart push values
+
+Adds methods for getting and setting a furnace minecart's push values
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java
+index 53042b75b45093535d6572239b34c3ff9a72f648..1be1f6d23f2224d4d8720d40f2e530736b1bae81 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftMinecartFurnace.java
+@@ -27,6 +27,30 @@ public class CraftMinecartFurnace extends CraftMinecart implements PoweredMineca
+ this.getHandle().fuel = fuel;
+ }
+
++ // Paper start
++ @Override
++ public double getPushX() {
++ return getHandle().push.x;
++ }
++
++ @Override
++ public double getPushZ() {
++ return getHandle().push.z;
++ }
++
++ @Override
++ public void setPushX(double xPush) {
++ final net.minecraft.world.phys.Vec3 push = getHandle().push;
++ getHandle().push = new net.minecraft.world.phys.Vec3(xPush, push.y, push.z);
++ }
++
++ @Override
++ public void setPushZ(double zPush) {
++ final net.minecraft.world.phys.Vec3 push = getHandle().push;
++ getHandle().push = new net.minecraft.world.phys.Vec3(push.x, push.y, zPush);
++ }
++ // Paper end
++
+ @Override
+ public String toString() {
+ return "CraftMinecartFurnace";
diff --git a/patches/server/0664-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch b/patches/server/0664-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch
new file mode 100644
index 0000000000..c98e7fd0f3
--- /dev/null
+++ b/patches/server/0664-Fix-cancelling-ProjectileHitEvent-for-piercing-arrow.patch
@@ -0,0 +1,40 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 19 Feb 2022 19:05:59 -0800
+Subject: [PATCH] Fix cancelling ProjectileHitEvent for piercing arrows
+
+Piercing arrows search for multiple entities inside a while
+loop that is checking the projectile entity's removed state.
+If the hit event is cancelled on the first entity, the event will
+be called over and over again inside that while loop until the event
+is not cancelled. The solution here, is to make use of an
+already-existing field on AbstractArrow for tracking entities hit by
+piercing arrows to avoid duplicate damage being applied.
+
+== AT ==
+protected net.minecraft.world.entity.projectile.Projectile hitCancelled
+
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java
+index aab7d546317d93876ccd0e02c0631ccc7c8f9bf5..0abe2fe6d7cf0a2084b7219c3ab0c5118586a8fe 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/AbstractArrow.java
+@@ -345,6 +345,19 @@ public abstract class AbstractArrow extends Projectile {
+
+ }
+
++ // Paper start - Fix cancelling ProjectileHitEvent for piercing arrows
++ @Override
++ public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult hitResult) {
++ if (hitResult instanceof EntityHitResult entityHitResult && this.hitCancelled && this.getPierceLevel() > 0) {
++ if (this.piercingIgnoreEntityIds == null) {
++ this.piercingIgnoreEntityIds = new IntOpenHashSet(5);
++ }
++ this.piercingIgnoreEntityIds.add(entityHitResult.getEntity().getId());
++ }
++ return super.preHitTargetOrDeflectSelf(hitResult);
++ }
++ // Paper end - Fix cancelling ProjectileHitEvent for piercing arrows
++
+ @Override
+ protected double getDefaultGravity() {
+ return 0.05D;
diff --git a/patches/server/0665-More-Projectile-API.patch b/patches/server/0665-More-Projectile-API.patch
new file mode 100644
index 0000000000..e5cadb6487
--- /dev/null
+++ b/patches/server/0665-More-Projectile-API.patch
@@ -0,0 +1,932 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Tue, 22 Jun 2021 23:41:11 -0400
+Subject: [PATCH] More Projectile API
+
+== AT ==
+public net.minecraft.world.entity.projectile.FishingHook timeUntilLured
+public net.minecraft.world.entity.projectile.FishingHook fishAngle
+public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaX
+public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaY
+public net.minecraft.world.entity.projectile.ShulkerBullet targetDeltaZ
+public net.minecraft.world.entity.projectile.ShulkerBullet currentMoveDirection
+public net.minecraft.world.entity.projectile.ShulkerBullet flightSteps
+public net.minecraft.world.entity.projectile.AbstractArrow soundEvent
+public net.minecraft.world.entity.projectile.AbstractArrow setPickupItemStack(Lnet/minecraft/world/item/ItemStack;)V
+public net.minecraft.world.entity.projectile.ThrownTrident dealtDamage
+public net.minecraft.world.entity.projectile.Arrow NO_EFFECT_COLOR
+public net.minecraft.world.entity.projectile.Projectile hasBeenShot
+public net.minecraft.world.entity.projectile.Projectile leftOwner
+public net.minecraft.world.entity.projectile.Projectile ownerUUID
+public net.minecraft.world.entity.projectile.Projectile preOnHit(Lnet/minecraft/world/phys/HitResult;)V
+public net.minecraft.world.entity.projectile.Projectile canHitEntity(Lnet/minecraft/world/entity/Entity;)Z
+public net.minecraft.world.entity.projectile.FireworkRocketEntity getDefaultItem()Lnet/minecraft/world/item/ItemStack;
+public net.minecraft.world.item.CrossbowItem FIREWORK_POWER
+
+Co-authored-by: Nassim Jahnke <[email protected]>
+Co-authored-by: SoSeDiK <[email protected]>
+Co-authored-by: MelnCat <[email protected]>
+Co-authored-by: Lulu13022002 <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
+index 966ba1a9c2e7923a970ce3799a5e2ef6ca36bf84..a2487ca0d7794e58d9ede35d9be5a05a7ea7b03c 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
+@@ -419,13 +419,18 @@ public class FishingHook extends Projectile {
+ }
+ } else {
+ // CraftBukkit start - logic to modify fishing wait time
+- this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime);
+- this.timeUntilLured -= (this.applyLure) ? (this.lureSpeed >= this.maxWaitTime ? this.timeUntilLured - 1 : this.lureSpeed ) : 0; // Paper - Fix Lure infinite loop
++ this.resetTimeUntilLured(); // Paper - more projectile api - extract time until lured reset logic
+ // CraftBukkit end
+ }
+ }
+
+ }
++ // Paper start - more projectile api - extract time until lured reset logic
++ public void resetTimeUntilLured() {
++ this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime);
++ this.timeUntilLured -= (this.applyLure) ? (this.lureSpeed >= this.maxWaitTime ? this.timeUntilLured - 1 : this.lureSpeed ) : 0; // Paper - Fix Lure infinite loop
++ }
++ // Paper end - more projectile api - extract time until lured reset logic
+
+ public boolean calculateOpenWater(BlockPos pos) {
+ FishingHook.OpenWaterType entityfishinghook_waterposition = FishingHook.OpenWaterType.INVALID;
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
+index 5cff58ab33657e7fb2642928e42b728e7b0b4689..f3781e37358f970b56e5b8de77ef224151270f1c 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
+@@ -288,7 +288,7 @@ public abstract class Projectile extends Entity implements TraceableEntity {
+ }
+
+ // CraftBukkit start - call projectile hit event
+- protected ProjectileDeflection preHitTargetOrDeflectSelf(HitResult movingobjectposition) {
++ public ProjectileDeflection preHitTargetOrDeflectSelf(HitResult movingobjectposition) { // Paper - protected -> public
+ org.bukkit.event.entity.ProjectileHitEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callProjectileHitEvent(this, movingobjectposition);
+ this.hitCancelled = event != null && event.isCancelled();
+ if (movingobjectposition.getType() == HitResult.Type.BLOCK || !this.hitCancelled) {
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
+index d6ac07d9d5ee0430a1d91b7084b378aac1d047e5..a486466040a646b8a5a5ff2430cdd25b95b7e20f 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
+@@ -103,8 +103,12 @@ public class ThrownPotion extends ThrowableItemProjectile {
+ @Override
+ protected void onHit(HitResult hitResult) {
+ super.onHit(hitResult);
++ // Paper start - More projectile API
++ this.splash(hitResult);
++ }
++ public void splash(@Nullable HitResult hitResult) {
++ // Paper end - More projectile API
+ Level world = this.level();
+-
+ if (world instanceof ServerLevel worldserver) {
+ ItemStack itemstack = this.getItem();
+ PotionContents potioncontents = (PotionContents) itemstack.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY);
+@@ -116,7 +120,7 @@ public class ThrownPotion extends ThrowableItemProjectile {
+ if (this.isLingering()) {
+ showParticles = this.makeAreaOfEffectCloud(potioncontents, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper
+ } else {
+- showParticles = this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper
++ showParticles = this.applySplash(worldserver, potioncontents.getAllEffects(), hitResult != null && hitResult.getType() == HitResult.Type.ENTITY ? ((EntityHitResult) hitResult).getEntity() : null, hitResult); // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API
+ }
+ }
+
+@@ -178,7 +182,7 @@ public class ThrownPotion extends ThrowableItemProjectile {
+
+ }
+
+- private boolean applySplash(ServerLevel worldserver, Iterable<MobEffectInstance> iterable, @Nullable Entity entity, HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events
++ private boolean applySplash(ServerLevel worldserver, Iterable<MobEffectInstance> iterable, @Nullable Entity entity, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - Fix potions splash events & More projectile API
+ AABB axisalignedbb = this.getBoundingBox().inflate(4.0D, 2.0D, 4.0D);
+ List<net.minecraft.world.entity.LivingEntity> list = worldserver.getEntitiesOfClass(net.minecraft.world.entity.LivingEntity.class, axisalignedbb);
+ Map<LivingEntity, Double> affected = new HashMap<LivingEntity, Double>(); // CraftBukkit
+@@ -256,7 +260,7 @@ public class ThrownPotion extends ThrowableItemProjectile {
+
+ }
+
+- private boolean makeAreaOfEffectCloud(PotionContents potioncontents, HitResult position) { // CraftBukkit - Pass MovingObjectPosition
++ private boolean makeAreaOfEffectCloud(PotionContents potioncontents, @Nullable HitResult position) { // CraftBukkit - Pass MovingObjectPosition // Paper - More projectile API
+ AreaEffectCloud entityareaeffectcloud = new AreaEffectCloud(this.level(), this.getX(), this.getY(), this.getZ());
+ Entity entity = this.getOwner();
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java
+index 91c2d0b40d3fca86938cd454e1415a4eea3df7c7..de4fb2654c7895cfd83ad694455ee56cb708c2f2 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/AbstractProjectile.java
+@@ -17,4 +17,65 @@ public abstract class AbstractProjectile extends CraftEntity implements Projecti
+ @Override
+ public void setBounce(boolean doesBounce) {}
+
++ // Paper start - More projectile API
++ @Override
++ public boolean hasLeftShooter() {
++ return this.getHandle().leftOwner;
++ }
++
++ @Override
++ public void setHasLeftShooter(boolean leftShooter) {
++ this.getHandle().leftOwner = leftShooter;
++ }
++
++ @Override
++ public boolean hasBeenShot() {
++ return this.getHandle().hasBeenShot;
++ }
++
++ @Override
++ public void setHasBeenShot(boolean beenShot) {
++ this.getHandle().hasBeenShot = beenShot;
++ }
++
++ @Override
++ public boolean canHitEntity(org.bukkit.entity.Entity entity) {
++ return this.getHandle().canHitEntity(((CraftEntity) entity).getHandle());
++ }
++
++ @Override
++ public void hitEntity(org.bukkit.entity.Entity entity) {
++ this.getHandle().preHitTargetOrDeflectSelf(new net.minecraft.world.phys.EntityHitResult(((CraftEntity) entity).getHandle()));
++ }
++
++ @Override
++ public void hitEntity(org.bukkit.entity.Entity entity, org.bukkit.util.Vector vector) {
++ this.getHandle().preHitTargetOrDeflectSelf(new net.minecraft.world.phys.EntityHitResult(((CraftEntity) entity).getHandle(), new net.minecraft.world.phys.Vec3(vector.getX(), vector.getY(), vector.getZ())));
++ }
++
++ @Override
++ public net.minecraft.world.entity.projectile.Projectile getHandle() {
++ return (net.minecraft.world.entity.projectile.Projectile) entity;
++ }
++
++ @Override
++ public final org.bukkit.projectiles.ProjectileSource getShooter() {
++ return this.getHandle().projectileSource;
++ }
++
++ @Override
++ public final void setShooter(org.bukkit.projectiles.ProjectileSource shooter) {
++ if (shooter instanceof CraftEntity craftEntity) {
++ this.getHandle().setOwner(craftEntity.getHandle());
++ } else {
++ this.getHandle().setOwner(null);
++ }
++ this.getHandle().projectileSource = shooter;
++ }
++
++ @Override
++ public java.util.UUID getOwnerUniqueId() {
++ return this.getHandle().ownerUUID;
++ }
++ // Paper end - More projectile API
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java
+index 0f85c1f991469b277bba8b40b087f7224b4b3a85..1f30109abd86b76af343eb5eb75ec3db83ef9417 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAbstractArrow.java
+@@ -59,20 +59,7 @@ public class CraftAbstractArrow extends AbstractProjectile implements AbstractAr
+ this.getHandle().setCritArrow(critical);
+ }
+
+- @Override
+- public ProjectileSource getShooter() {
+- return this.getHandle().projectileSource;
+- }
+-
+- @Override
+- public void setShooter(ProjectileSource shooter) {
+- if (shooter instanceof Entity) {
+- this.getHandle().setOwner(((CraftEntity) shooter).getHandle());
+- } else {
+- this.getHandle().setOwner(null);
+- }
+- this.getHandle().projectileSource = shooter;
+- }
++ // Paper - moved to AbstractProjectile
+
+ @Override
+ public boolean isInBlock() {
+@@ -133,6 +120,7 @@ public class CraftAbstractArrow extends AbstractProjectile implements AbstractAr
+
+ @Override
+ public ItemStack getWeapon() {
++ if (this.getHandle().getWeaponItem() == null) return null; // Paper - fix NPE
+ return CraftItemStack.asBukkitCopy(this.getHandle().getWeaponItem());
+ }
+
+@@ -152,4 +140,37 @@ public class CraftAbstractArrow extends AbstractProjectile implements AbstractAr
+ public String toString() {
+ return "CraftArrow";
+ }
++
++ // Paper start
++ @Override
++ public CraftItemStack getItemStack() {
++ return CraftItemStack.asCraftMirror(this.getHandle().getPickupItem());
++ }
++
++ @Override
++ public void setItemStack(final ItemStack stack) {
++ Preconditions.checkArgument(stack != null, "ItemStack cannot be null");
++ this.getHandle().setPickupItemStack(CraftItemStack.asNMSCopy(stack));
++ }
++
++ @Override
++ public void setLifetimeTicks(int ticks) {
++ this.getHandle().life = ticks;
++ }
++
++ @Override
++ public int getLifetimeTicks() {
++ return this.getHandle().life;
++ }
++
++ @Override
++ public org.bukkit.Sound getHitSound() {
++ return org.bukkit.craftbukkit.CraftSound.minecraftToBukkit(this.getHandle().soundEvent);
++ }
++
++ @Override
++ public void setHitSound(org.bukkit.Sound sound) {
++ this.getHandle().setSoundEvent(org.bukkit.craftbukkit.CraftSound.bukkitToMinecraft(sound));
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java
+index 6591513bb62226b6f85fd2ef9f6ebe376f0f7362..f9c113dc018702159345240d6d0de85767afa0c3 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftAreaEffectCloud.java
+@@ -125,7 +125,7 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud
+
+ @Override
+ public Color getColor() {
+- return Color.fromRGB(this.getHandle().potionContents.getColor());
++ return Color.fromRGB(this.getHandle().potionContents.getColor() & 0x00FFFFFF); // Paper - skip alpha channel
+ }
+
+ @Override
+@@ -143,7 +143,7 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud
+ this.removeCustomEffect(effect.getType());
+ }
+ this.getHandle().addEffect(CraftPotionUtil.fromBukkit(effect));
+- this.getHandle().updateColor();
++ // this.getHandle().updateColor(); // Paper - already done above
+ return true;
+ }
+
+@@ -151,7 +151,7 @@ public class CraftAreaEffectCloud extends CraftEntity implements AreaEffectCloud
+ public void clearCustomEffects() {
+ PotionContents old = this.getHandle().potionContents;
+ this.getHandle().setPotionContents(new PotionContents(old.potion(), old.customColor(), List.of(), old.customName()));
+- this.getHandle().updateColor();
++ // this.getHandle().updateColor(); // Paper - already done above
+ }
+
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java
+index 199d5836dc787cca54c6b653a4e67573f2f758a2..15d50a284cafc2eb59239ca00926836526f09e06 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftArrow.java
+@@ -43,7 +43,7 @@ public class CraftArrow extends CraftAbstractArrow implements Arrow {
+ this.removeCustomEffect(effect.getType());
+ }
+ this.getHandle().addEffect(CraftPotionUtil.fromBukkit(effect));
+- this.getHandle().updateColor();
++ // this.getHandle().updateColor(); // Paper - already done above
+ return true;
+ }
+
+@@ -51,7 +51,7 @@ public class CraftArrow extends CraftAbstractArrow implements Arrow {
+ public void clearCustomEffects() {
+ PotionContents old = this.getHandle().getPotionContents();
+ this.getHandle().setPotionContents(new PotionContents(old.potion(), old.customColor(), List.of(), old.customName()));
+- this.getHandle().updateColor();
++ // this.getHandle().updateColor(); // Paper - already done above
+ }
+
+ @Override
+@@ -117,16 +117,17 @@ public class CraftArrow extends CraftAbstractArrow implements Arrow {
+
+ @Override
+ public void setColor(Color color) {
+- int colorRGB = (color == null) ? -1 : color.asRGB();
++ int colorRGB = (color == null) ? net.minecraft.world.entity.projectile.Arrow.NO_EFFECT_COLOR : color.asARGB(); // Paper
+ PotionContents old = this.getHandle().getPotionContents();
+ this.getHandle().setPotionContents(new PotionContents(old.potion(), Optional.of(colorRGB), old.customEffects(), old.customName()));
+ }
+
+ @Override
+ public Color getColor() {
+- if (this.getHandle().getColor() <= -1) {
++ int color = this.getHandle().getColor(); // Paper
++ if (color == net.minecraft.world.entity.projectile.Arrow.NO_EFFECT_COLOR) { // Paper
+ return null;
+ }
+- return Color.fromRGB(this.getHandle().getColor());
++ return Color.fromARGB(color); // Paper
+ }
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
+index f3ad9c57ce2fc06b90b7ab7e1f31ef0e1b564c9f..2a32f2aa3cb7395900cf2d06cc2dcd4ddacd4145 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityTypes.java
+@@ -440,7 +440,7 @@ public final class CraftEntityTypes {
+ BlockPos pos = BlockPos.containing(spawnData.x(), spawnData.y(), spawnData.z());
+ return new FallingBlockEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), spawnData.world().getBlockState(pos)); // Paper - create falling block entities correctly
+ }));
+- register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), net.minecraft.world.item.ItemStack.EMPTY)));
++ register(new EntityTypeData<>(EntityType.FIREWORK_ROCKET, Firework.class, CraftFirework::new, spawnData -> new FireworkRocketEntity(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), FireworkRocketEntity.getDefaultItem()))); // Paper - pass correct default to rocket for data storage
+ register(new EntityTypeData<>(EntityType.EVOKER_FANGS, EvokerFangs.class, CraftEvokerFangs::new, spawnData -> new net.minecraft.world.entity.projectile.EvokerFangs(spawnData.minecraftWorld(), spawnData.x(), spawnData.y(), spawnData.z(), (float) Math.toRadians(spawnData.yaw()), 0, null)));
+ register(new EntityTypeData<>(EntityType.COMMAND_BLOCK_MINECART, CommandMinecart.class, CraftMinecartCommand::new, createMinecart(net.minecraft.world.entity.EntityType.COMMAND_BLOCK_MINECART)));
+ register(new EntityTypeData<>(EntityType.MINECART, RideableMinecart.class, CraftMinecartRideable::new, createMinecart(net.minecraft.world.entity.EntityType.MINECART)));
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java
+index 1b084d63bdbb24dad45d28eed1693eb6e26e24dc..43d7bea201a52cfeacf60c75caa28dfd2c4ff164 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFireball.java
+@@ -34,20 +34,7 @@ public class CraftFireball extends AbstractProjectile implements Fireball {
+ this.getHandle().bukkitYield = yield;
+ }
+
+- @Override
+- public ProjectileSource getShooter() {
+- return this.getHandle().projectileSource;
+- }
+-
+- @Override
+- public void setShooter(ProjectileSource shooter) {
+- if (shooter instanceof CraftLivingEntity) {
+- this.getHandle().setOwner(((CraftLivingEntity) shooter).getHandle());
+- } else {
+- this.getHandle().setOwner(null);
+- }
+- this.getHandle().projectileSource = shooter;
+- }
++ // Paper - moved to AbstractProjectile
+
+ @Override
+ public Vector getDirection() {
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java
+index c9e15a9d82dee935293b2e7e233f5b9b2d822448..2d54cf6f3d9696c55335f0a2057025e2034d4e13 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFirework.java
+@@ -15,24 +15,26 @@ import org.bukkit.inventory.meta.FireworkMeta;
+ public class CraftFirework extends CraftProjectile implements Firework {
+
+ private final Random random = new Random();
+- private final CraftItemStack item;
++ //private CraftItemStack item; // Paper - Remove usage, not accurate representation of current item.
+
+ public CraftFirework(CraftServer server, FireworkRocketEntity entity) {
+ super(server, entity);
+
+- ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM);
+-
+- if (item.isEmpty()) {
+- item = new ItemStack(Items.FIREWORK_ROCKET);
+- this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item);
+- }
+-
+- this.item = CraftItemStack.asCraftMirror(item);
+-
+- // Ensure the item is a firework...
+- if (this.item.getType() != Material.FIREWORK_ROCKET) {
+- this.item.setType(Material.FIREWORK_ROCKET);
+- }
++ // Paper start - Expose firework item directly
++// ItemStack item = this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM);
++//
++// if (item.isEmpty()) {
++// item = new ItemStack(Items.FIREWORK_ROCKET);
++// this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item);
++// }
++//
++// this.item = CraftItemStack.asCraftMirror(item);
++//
++// // Ensure the item is a firework...
++// if (this.item.getType() != Material.FIREWORK_ROCKET) {
++// this.item.setType(Material.FIREWORK_ROCKET);
++// }
++ // Paper end - Expose firework item directly
+ }
+
+ @Override
+@@ -47,12 +49,12 @@ public class CraftFirework extends CraftProjectile implements Firework {
+
+ @Override
+ public FireworkMeta getFireworkMeta() {
+- return (FireworkMeta) this.item.getItemMeta();
++ return (FireworkMeta) CraftItemStack.getItemMeta(this.getHandle().getEntityData().get(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM), org.bukkit.inventory.ItemType.FIREWORK_ROCKET); // Paper - Expose firework item directly
+ }
+
+ @Override
+ public void setFireworkMeta(FireworkMeta meta) {
+- this.item.setItemMeta(meta);
++ applyFireworkEffect(meta); // Paper - Expose firework item directly
+
+ // Copied from EntityFireworks constructor, update firework lifetime/power
+ this.getHandle().lifetime = 10 * (1 + meta.getPower()) + this.random.nextInt(6) + this.random.nextInt(7);
+@@ -136,4 +138,46 @@ public class CraftFirework extends CraftProjectile implements Firework {
+ return getHandle().spawningEntity;
+ }
+ // Paper end
++ // Paper start - Expose firework item directly + manually setting flight
++ @Override
++ public org.bukkit.inventory.ItemStack getItem() {
++ return CraftItemStack.asBukkitCopy(this.getHandle().getItem());
++ }
++
++ @Override
++ public void setItem(org.bukkit.inventory.ItemStack itemStack) {
++ FireworkMeta meta = getFireworkMeta();
++ ItemStack nmsItem = itemStack == null ? FireworkRocketEntity.getDefaultItem() : CraftItemStack.asNMSCopy(itemStack);
++ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, nmsItem);
++
++ applyFireworkEffect(meta);
++ }
++
++ @Override
++ public int getTicksFlown() {
++ return this.getHandle().life;
++ }
++
++ @Override
++ public void setTicksFlown(int ticks) {
++ this.getHandle().life = ticks;
++ }
++
++ @Override
++ public int getTicksToDetonate() {
++ return this.getHandle().lifetime;
++ }
++
++ @Override
++ public void setTicksToDetonate(int ticks) {
++ this.getHandle().lifetime = ticks;
++ }
++
++ void applyFireworkEffect(FireworkMeta meta) {
++ ItemStack item = this.getHandle().getItem();
++ CraftItemStack.applyMetaToItem(item, meta);
++
++ this.getHandle().getEntityData().set(FireworkRocketEntity.DATA_ID_FIREWORKS_ITEM, item);
++ }
++ // Paper end - Expose firework item directly + manually setting flight
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java
+index 6e2f91423371ead9890095cf4b1e2299c4dcba28..9d8f4b7176e60180565e3134a14ecf19060f2621 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java
+@@ -196,4 +196,42 @@ public class CraftFishHook extends CraftProjectile implements FishHook {
+ public HookState getState() {
+ return HookState.values()[this.getHandle().currentState.ordinal()];
+ }
++ // Paper start - More FishHook API
++ @Override
++ public int getWaitTime() {
++ return this.getHandle().timeUntilLured;
++ }
++
++ @Override
++ public void setWaitTime(int ticks) {
++ this.getHandle().timeUntilLured = ticks;
++ }
++
++ @Override
++ public int getTimeUntilBite() {
++ return this.getHandle().timeUntilHooked;
++ }
++
++ @Override
++ public void setTimeUntilBite(final int ticks) {
++ com.google.common.base.Preconditions.checkArgument(ticks >= 1, "Cannot set time until bite to less than 1 (%s<1)", ticks);
++ final FishingHook hook = this.getHandle();
++
++ // Reset the fish angle hook only when this call "enters" the fish into the lure stage.
++ final boolean alreadyInLuringPhase = hook.timeUntilHooked > 0 && hook.timeUntilLured <= 0;
++ if (!alreadyInLuringPhase) {
++ hook.fishAngle = net.minecraft.util.Mth.nextFloat(hook.random, hook.minLureAngle, hook.maxLureAngle);
++ hook.timeUntilLured = 0;
++ }
++
++ hook.timeUntilHooked = ticks;
++ }
++
++ @Override
++ public void resetFishingState() {
++ final FishingHook hook = this.getHandle();
++ hook.resetTimeUntilLured();
++ hook.timeUntilHooked = 0; // Reset time until hooked, will be repopulated once lured time is ticked down.
++ }
++ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+index 10a95c1a40597867ffd2974037bfed86dd6deda4..2028f9874e96a3b274528858d4149b04a075f256 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLivingEntity.java
+@@ -579,8 +579,15 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+ }
+
+ @Override
+- @SuppressWarnings("unchecked")
+ public <T extends Projectile> T launchProjectile(Class<? extends T> projectile, Vector velocity) {
++ // Paper start - launchProjectile consumer
++ return this.launchProjectile(projectile, velocity, null);
++ }
++
++ @Override
++ @SuppressWarnings("unchecked")
++ public <T extends Projectile> T launchProjectile(Class<? extends T> projectile, Vector velocity, java.util.function.Consumer<? super T> function) {
++ // Paper end - launchProjectile consumer
+ Preconditions.checkState(!this.getHandle().generation, "Cannot launch projectile during world generation");
+
+ net.minecraft.world.level.Level world = ((CraftWorld) this.getWorld()).getHandle();
+@@ -606,7 +613,7 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+ } else {
+ launch = new net.minecraft.world.entity.projectile.Arrow(world, this.getHandle(), new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.ARROW), null);
+ }
+- ((net.minecraft.world.entity.projectile.AbstractArrow) launch).shootFromRotation(this.getHandle(), this.getHandle().getXRot(), this.getHandle().getYRot(), 0.0F, 3.0F, 1.0F); // ItemBow
++ ((net.minecraft.world.entity.projectile.AbstractArrow) launch).shootFromRotation(this.getHandle(), this.getHandle().getXRot(), this.getHandle().getYRot(), 0.0F, Trident.class.isAssignableFrom(projectile) ? net.minecraft.world.item.TridentItem.SHOOT_POWER : 3.0F, 1.0F); // ItemBow // Paper - see TridentItem
+ } else if (ThrownPotion.class.isAssignableFrom(projectile)) {
+ if (LingeringPotion.class.isAssignableFrom(projectile)) {
+ launch = new net.minecraft.world.entity.projectile.ThrownPotion(world, this.getHandle(), new net.minecraft.world.item.ItemStack(Items.LINGERING_POTION));
+@@ -663,8 +670,26 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+ } else if (Firework.class.isAssignableFrom(projectile)) {
+ Location location = this.getEyeLocation();
+
+- launch = new FireworkRocketEntity(world, net.minecraft.world.item.ItemStack.EMPTY, this.getHandle());
+- launch.moveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch());
++ // Paper start - see CrossbowItem
++ launch = new FireworkRocketEntity(world, FireworkRocketEntity.getDefaultItem(), this.getHandle(), location.getX(), location.getY() - 0.15F, location.getZ(), true); // Paper - pass correct default to rocket for data storage & see CrossbowItem for regular launch without elytra boost
++
++ // Lifted from net.minecraft.world.item.ProjectileWeaponItem.shoot
++ float f2 = /* net.minecraft.world.item.enchantment.EnchantmentHelper.processProjectileSpread((ServerLevel) world, new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.CROSSBOW), this.getHandle(), 0.0F); */ 0; // Just shortcut this to 0, no need to do any calculations on a non existing stack
++ int projectileSize = 1;
++ int i = 0;
++
++ float f3 = projectileSize == 1 ? 0.0F : 2.0F * f2 / (float) (projectileSize - 1);
++ float f4 = (float) ((projectileSize - 1) % 2) * f3 / 2.0F;
++ float f5 = 1.0F;
++ float yaw = f4 + f5 * (float) ((i + 1) / 2) * f3;
++
++ // Lifted from net.minecraft.world.item.CrossbowItem.shootProjectile
++ Vec3 vec3 = this.getHandle().getUpVector(1.0F);
++ org.joml.Quaternionf quaternionf = new org.joml.Quaternionf().setAngleAxis((double)(yaw * (float) (Math.PI / 180.0)), vec3.x, vec3.y, vec3.z);
++ Vec3 vec32 = this.getHandle().getViewVector(1.0F);
++ org.joml.Vector3f vector3f = vec32.toVector3f().rotate(quaternionf);
++ ((FireworkRocketEntity) launch).shoot((double)vector3f.x(), (double)vector3f.y(), (double)vector3f.z(), net.minecraft.world.item.CrossbowItem.FIREWORK_POWER, 1.0F);
++ // Paper end
+ }
+
+ Preconditions.checkArgument(launch != null, "Projectile (%s) not supported", projectile.getName());
+@@ -672,6 +697,11 @@ public class CraftLivingEntity extends CraftEntity implements LivingEntity {
+ if (velocity != null) {
+ ((T) launch.getBukkitEntity()).setVelocity(velocity);
+ }
++ // Paper start - launchProjectile consumer
++ if (function != null) {
++ function.accept((T) launch.getBukkitEntity());
++ }
++ // Paper end - launchProjectile consumer
+
+ world.addFreshEntity(launch);
+ return (T) launch.getBukkitEntity();
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java
+index 70cbc6c668c60e9d608ca7013b72f9b916c05c2d..47633f05b4fab1dcabc2117e7645fe6d6949622a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftLlamaSpit.java
+@@ -20,13 +20,5 @@ public class CraftLlamaSpit extends AbstractProjectile implements LlamaSpit {
+ return "CraftLlamaSpit";
+ }
+
+- @Override
+- public ProjectileSource getShooter() {
+- return (this.getHandle().getOwner() != null) ? (ProjectileSource) this.getHandle().getOwner().getBukkitEntity() : null;
+- }
+-
+- @Override
+- public void setShooter(ProjectileSource source) {
+- this.getHandle().setOwner((source != null) ? ((CraftLivingEntity) source).getHandle() : null);
+- }
++ // Paper - moved to AbstractProjectile
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java
+index 696fdfa723aa896a67946f862d7c6890f7f7ab17..4f1fa7dec78970bdfc184d3c1f1632dc9d75a574 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftProjectile.java
+@@ -10,20 +10,7 @@ public abstract class CraftProjectile extends AbstractProjectile implements Proj
+ super(server, entity);
+ }
+
+- @Override
+- public ProjectileSource getShooter() {
+- return this.getHandle().projectileSource;
+- }
+-
+- @Override
+- public void setShooter(ProjectileSource shooter) {
+- if (shooter instanceof CraftLivingEntity) {
+- this.getHandle().setOwner((LivingEntity) ((CraftLivingEntity) shooter).entity);
+- } else {
+- this.getHandle().setOwner(null);
+- }
+- this.getHandle().projectileSource = shooter;
+- }
++ // Paper - moved to AbstractProjectile
+
+ @Override
+ public net.minecraft.world.entity.projectile.Projectile getHandle() {
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java
+index d685d09cae5f862c0004f148298c800736d2139e..b3797a43eeee11cb7ae0774d61bd5f195d0aa3ad 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftShulkerBullet.java
+@@ -12,31 +12,56 @@ public class CraftShulkerBullet extends AbstractProjectile implements ShulkerBul
+ super(server, entity);
+ }
+
++ // Paper - moved to AbstractProjectile
++
++ @Override
++ public org.bukkit.entity.Entity getTarget() {
++ return this.getHandle().getTarget() != null ? this.getHandle().getTarget().getBukkitEntity() : null;
++ }
++
+ @Override
+- public ProjectileSource getShooter() {
+- return this.getHandle().projectileSource;
++ public void setTarget(org.bukkit.entity.Entity target) {
++ Preconditions.checkState(!this.getHandle().generation, "Cannot set target during world generation");
++
++ this.getHandle().setTarget(target == null ? null : ((CraftEntity) target).getHandle());
+ }
+
+ @Override
+- public void setShooter(ProjectileSource shooter) {
+- if (shooter instanceof Entity) {
+- this.getHandle().setOwner(((CraftEntity) shooter).getHandle());
+- } else {
+- this.getHandle().setOwner(null);
++ public org.bukkit.util.Vector getTargetDelta() {
++ net.minecraft.world.entity.projectile.ShulkerBullet bullet = this.getHandle();
++ return new org.bukkit.util.Vector(bullet.targetDeltaX, bullet.targetDeltaY, bullet.targetDeltaZ);
++ }
++
++ @Override
++ public void setTargetDelta(org.bukkit.util.Vector vector) {
++ net.minecraft.world.entity.projectile.ShulkerBullet bullet = this.getHandle();
++ bullet.targetDeltaX = vector.getX();
++ bullet.targetDeltaY = vector.getY();
++ bullet.targetDeltaZ = vector.getZ();
++ }
++
++ @Override
++ public org.bukkit.block.BlockFace getCurrentMovementDirection() {
++ net.minecraft.core.Direction dir = this.getHandle().currentMoveDirection;
++ if (dir == null) {
++ return null; // random dir
+ }
+- this.getHandle().projectileSource = shooter;
++ return org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(dir);
+ }
+
+ @Override
+- public org.bukkit.entity.Entity getTarget() {
+- return this.getHandle().getTarget() != null ? this.getHandle().getTarget().getBukkitEntity() : null;
++ public void setCurrentMovementDirection(org.bukkit.block.BlockFace movementDirection) {
++ this.getHandle().currentMoveDirection = org.bukkit.craftbukkit.block.CraftBlock.blockFaceToNotch(movementDirection);
+ }
+
+ @Override
+- public void setTarget(org.bukkit.entity.Entity target) {
+- Preconditions.checkState(!this.getHandle().generation, "Cannot set target during world generation");
++ public int getFlightSteps() {
++ return this.getHandle().flightSteps;
++ }
+
+- this.getHandle().setTarget(target == null ? null : ((CraftEntity) target).getHandle());
++ @Override
++ public void setFlightSteps(int steps) {
++ this.getHandle().flightSteps = steps;
+ }
+
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java
+index d67a80161b3e7c1fe02a6ed9d341c00dc7c2847a..f6fa6f1ac50b757dd3bc9a8dee9f6085446182c8 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftThrownPotion.java
+@@ -36,11 +36,31 @@ public class CraftThrownPotion extends CraftThrowableProjectile implements Throw
+ @Override
+ public void setItem(ItemStack item) {
+ Preconditions.checkArgument(item != null, "ItemStack cannot be null");
+- Preconditions.checkArgument(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack material must be Material.LINGERING_POTION or Material.SPLASH_POTION but was Material.%s", item.getType());
++ // Preconditions.checkArgument(item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION, "ItemStack material must be Material.LINGERING_POTION or Material.SPLASH_POTION but was Material.%s", item.getType()); // Paper - Projectile API
++ org.bukkit.inventory.meta.PotionMeta meta = (item.getType() == Material.LINGERING_POTION || item.getType() == Material.SPLASH_POTION) ? null : this.getPotionMeta(); // Paper - Projectile API
+
+ this.getHandle().setItem(CraftItemStack.asNMSCopy(item));
++ if (meta != null) this.setPotionMeta(meta); // Paper - Projectile API
+ }
+
++ // Paper start - Projectile API
++ @Override
++ public org.bukkit.inventory.meta.PotionMeta getPotionMeta() {
++ return (org.bukkit.inventory.meta.PotionMeta) CraftItemStack.getItemMeta(this.getHandle().getItem(), org.bukkit.inventory.ItemType.SPLASH_POTION);
++ }
++
++ @Override
++ public void setPotionMeta(org.bukkit.inventory.meta.PotionMeta meta) {
++ net.minecraft.world.item.ItemStack item = this.getHandle().getItem();
++ CraftItemStack.applyMetaToItem(item, meta);
++ this.getHandle().setItem(item); // Reset item
++ }
++
++ @Override
++ public void splash() {
++ this.getHandle().splash(null);
++ }
++ // Paper end
+ @Override
+ public net.minecraft.world.entity.projectile.ThrownPotion getHandle() {
+ return (net.minecraft.world.entity.projectile.ThrownPotion) this.entity;
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java
+index e374b9f40eddca13b30855d25a2030f8df98138f..4fc893378fb0568ddcffc7593d66df6bfe23f659 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftTrident.java
+@@ -53,5 +53,15 @@ public class CraftTrident extends CraftAbstractArrow implements Trident {
+ com.google.common.base.Preconditions.checkArgument(loyaltyLevel >= 0 && loyaltyLevel <= 127, "The loyalty level has to be between 0 and 127");
+ this.getHandle().setLoyalty((byte) loyaltyLevel);
+ }
++
++ @Override
++ public boolean hasDealtDamage() {
++ return this.getHandle().dealtDamage;
++ }
++
++ @Override
++ public void setHasDealtDamage(boolean hasDealtDamage) {
++ this.getHandle().dealtDamage = hasDealtDamage;
++ }
+ // Paper end
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+index a5285a8952b2d99bfbb928b1bb31d3ddcf8ed57b..ca094e393de32b64db59c9fe906433761d70d29b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+@@ -841,19 +841,19 @@ public class CraftEventFactory {
+ /**
+ * PotionSplashEvent
+ */
+- public static PotionSplashEvent callPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, HitResult position, Map<LivingEntity, Double> affectedEntities) {
++ public static PotionSplashEvent callPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult position, Map<LivingEntity, Double> affectedEntities) { // Paper - nullable hitResult
+ ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity();
+
+ Block hitBlock = null;
+ BlockFace hitFace = null;
+- if (position.getType() == HitResult.Type.BLOCK) {
++ if (position != null && position.getType() == HitResult.Type.BLOCK) { // Paper - nullable hitResult
+ BlockHitResult positionBlock = (BlockHitResult) position;
+ hitBlock = CraftBlock.at(potion.level(), positionBlock.getBlockPos());
+ hitFace = CraftBlock.notchToBlockFace(positionBlock.getDirection());
+ }
+
+ org.bukkit.entity.Entity hitEntity = null;
+- if (position.getType() == HitResult.Type.ENTITY) {
++ if (position != null && position.getType() == HitResult.Type.ENTITY) { // Paper - nullable hitResult
+ hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity();
+ }
+
+@@ -862,20 +862,20 @@ public class CraftEventFactory {
+ return event;
+ }
+
+- public static LingeringPotionSplashEvent callLingeringPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, HitResult position, net.minecraft.world.entity.AreaEffectCloud cloud) {
++ public static LingeringPotionSplashEvent callLingeringPotionSplashEvent(net.minecraft.world.entity.projectile.ThrownPotion potion, @Nullable HitResult position, net.minecraft.world.entity.AreaEffectCloud cloud) { // Paper - nullable hitResult
+ ThrownPotion thrownPotion = (ThrownPotion) potion.getBukkitEntity();
+ AreaEffectCloud effectCloud = (AreaEffectCloud) cloud.getBukkitEntity();
+
+ Block hitBlock = null;
+ BlockFace hitFace = null;
+- if (position.getType() == HitResult.Type.BLOCK) {
++ if (position != null && position.getType() == HitResult.Type.BLOCK) { // Paper
+ BlockHitResult positionBlock = (BlockHitResult) position;
+ hitBlock = CraftBlock.at(potion.level(), positionBlock.getBlockPos());
+ hitFace = CraftBlock.notchToBlockFace(positionBlock.getDirection());
+ }
+
+ org.bukkit.entity.Entity hitEntity = null;
+- if (position.getType() == HitResult.Type.ENTITY) {
++ if (position != null && position.getType() == HitResult.Type.ENTITY) { // Paper
+ hitEntity = ((EntityHitResult) position).getEntity().getBukkitEntity();
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
+index 60062ea5b18b95a14c459f2f3a0743c1e1ac0f12..502be683e8b04a9966043c9bee9d9fe793b12ef5 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
+@@ -341,12 +341,23 @@ public final class CraftItemStack extends ItemStack {
+ public ItemMeta getItemMeta() {
+ return CraftItemStack.getItemMeta(this.handle);
+ }
++ // Paper start
++ public static void applyMetaToItem(net.minecraft.world.item.ItemStack itemStack, ItemMeta itemMeta) {
++ final CraftMetaItem.Applicator tag = new CraftMetaItem.Applicator();
++ ((CraftMetaItem) itemMeta).applyToItem(tag);
++ itemStack.applyComponents(tag.build());
++ }
+
+ public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item) {
++ return getItemMeta(item, null);
++ }
++ public static ItemMeta getItemMeta(net.minecraft.world.item.ItemStack item, org.bukkit.inventory.ItemType metaForType) {
++ // Paper end
+ if (!CraftItemStack.hasItemMeta(item)) {
+ return CraftItemFactory.instance().getItemMeta(CraftItemStack.getType(item));
+ }
+
++ if (metaForType != null) { return ((CraftItemType<?>) metaForType).getItemMeta(item); } // Paper
+ return ((CraftItemType<?>) CraftItemType.minecraftToBukkitNew(item.getItem())).getItemMeta(item);
+ }
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java
+index 642f5bf75661eb485558bc227f668e84416f3b5f..76fd4d27730d9139caa67099a6757ea33d681be9 100644
+--- a/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java
++++ b/src/main/java/org/bukkit/craftbukkit/projectiles/CraftBlockProjectileSource.java
+@@ -56,7 +56,15 @@ public class CraftBlockProjectileSource implements BlockProjectileSource {
+
+ @Override
+ public <T extends Projectile> T launchProjectile(Class<? extends T> projectile, Vector velocity) {
++ // Paper start - launchProjectile consumer
++ return this.launchProjectile(projectile, velocity, null);
++ }
++
++ @Override
++ public <T extends Projectile> T launchProjectile(Class<? extends T> projectile, Vector velocity, java.util.function.Consumer<? super T> function) {
++ // Paper end - launchProjectile consumer
+ Preconditions.checkArgument(this.getBlock().getType() == Material.DISPENSER, "Block is no longer dispenser");
++
+ // Copied from BlockDispenser.dispense()
+ BlockSource sourceblock = new BlockSource((ServerLevel) this.dispenserBlock.getLevel(), this.dispenserBlock.getBlockPos(), this.dispenserBlock.getBlockState(), this.dispenserBlock);
+ // Copied from DispenseBehaviorProjectile
+@@ -68,7 +76,7 @@ public class CraftBlockProjectileSource implements BlockProjectileSource {
+ item = Items.SNOWBALL;
+ } else if (Egg.class.isAssignableFrom(projectile)) {
+ item = Items.EGG;
+- } else if (EnderPearl.class.isAssignableFrom(projectile)) {
++ } else if (false && EnderPearl.class.isAssignableFrom(projectile)) { // Paper - more projectile API - disallow enderpearl, it is not a projectile item
+ item = Items.ENDER_PEARL;
+ } else if (ThrownExpBottle.class.isAssignableFrom(projectile)) {
+ item = Items.EXPERIENCE_BOTTLE;
+@@ -83,20 +91,20 @@ public class CraftBlockProjectileSource implements BlockProjectileSource {
+ item = Items.TIPPED_ARROW;
+ } else if (SpectralArrow.class.isAssignableFrom(projectile)) {
+ item = Items.SPECTRAL_ARROW;
+- } else {
++ } else if (org.bukkit.entity.Arrow.class.isAssignableFrom(projectile)) { // Paper - more projectile API - disallow trident
+ item = Items.ARROW;
+ }
+ } else if (Fireball.class.isAssignableFrom(projectile)) {
+- if (AbstractWindCharge.class.isAssignableFrom(projectile)) {
++ if (org.bukkit.entity.WindCharge.class.isAssignableFrom(projectile)) { // Paper - more projectile API - only allow wind charge not breeze wind charge
+ item = Items.WIND_CHARGE;
+- } else {
++ } else if (org.bukkit.entity.SmallFireball.class.isAssignableFrom(projectile)) { // Paper - more projectile API - only allow firing fire charges.
+ item = Items.FIRE_CHARGE;
+ }
+ } else if (Firework.class.isAssignableFrom(projectile)) {
+ item = Items.FIREWORK_ROCKET;
+ }
+
+- Preconditions.checkArgument(item instanceof ProjectileItem, "Projectile not supported");
++ Preconditions.checkArgument(item instanceof ProjectileItem, "Projectile '%s' not supported", projectile.getSimpleName()); // Paper - more projectile API - include simple name in exception
+
+ ItemStack itemstack = new ItemStack(item);
+ ProjectileItem projectileItem = (ProjectileItem) item;
+@@ -105,7 +113,7 @@ public class CraftBlockProjectileSource implements BlockProjectileSource {
+ Position iposition = dispenseConfig.positionFunction().getDispensePosition(sourceblock, enumdirection);
+ net.minecraft.world.entity.projectile.Projectile launch = projectileItem.asProjectile(world, iposition, itemstack, enumdirection);
+
+- if (Fireball.class.isAssignableFrom(projectile)) {
++ if (false && Fireball.class.isAssignableFrom(projectile)) { // Paper - more project API - dispensers cannot launch anything but fire charges.
+ AbstractHurtingProjectile customFireball = null;
+ if (WitherSkull.class.isAssignableFrom(projectile)) {
+ launch = customFireball = EntityType.WITHER_SKULL.create(world, EntitySpawnReason.TRIGGERED);
+@@ -130,7 +138,7 @@ public class CraftBlockProjectileSource implements BlockProjectileSource {
+ }
+ }
+
+- if (launch instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) {
++ if (false && launch instanceof net.minecraft.world.entity.projectile.AbstractArrow arrow) { // Paper - more projectile API - this is set by the respective ArrowItem when constructing the projectile
+ arrow.pickup = net.minecraft.world.entity.projectile.AbstractArrow.Pickup.ALLOWED;
+ }
+ launch.projectileSource = this;
+@@ -139,6 +147,11 @@ public class CraftBlockProjectileSource implements BlockProjectileSource {
+ if (velocity != null) {
+ ((T) launch.getBukkitEntity()).setVelocity(velocity);
+ }
++ // Paper start
++ if (function != null) {
++ function.accept((T) launch.getBukkitEntity());
++ }
++ // Paper end
+
+ world.addFreshEntity(launch);
+ return (T) launch.getBukkitEntity();
diff --git a/patches/server/0666-Fix-swamp-hut-cat-generation-deadlock.patch b/patches/server/0666-Fix-swamp-hut-cat-generation-deadlock.patch
new file mode 100644
index 0000000000..ed87313991
--- /dev/null
+++ b/patches/server/0666-Fix-swamp-hut-cat-generation-deadlock.patch
@@ -0,0 +1,64 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Sat, 12 Mar 2022 06:31:13 -0800
+Subject: [PATCH] Fix swamp hut cat generation deadlock
+
+The worldgen thread will attempt to get structure references
+via the world's getChunkAt method, which is fine if the gen is
+not cancelled - but if the chunk was unloaded, the call will block
+indefinitely. Instead of using the world state, we use the already
+supplied ServerLevelAccessor which will always have the chunk available.
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java
+index 93d8d45a9a8108e58f3a96d77dd35c7584133835..629b282cf27806ff37d67f83d44c06a9f32a9185 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/Cat.java
++++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java
+@@ -365,7 +365,7 @@ public class Cat extends TamableAnimal implements VariantHolder<Holder<CatVarian
+ BuiltInRegistries.CAT_VARIANT.getRandomElementOf(tagkey, world.getRandom()).ifPresent(this::setVariant);
+ ServerLevel worldserver = world.getLevel();
+
+- if (worldserver.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK).isValid()) {
++ if (worldserver.structureManager().getStructureWithPieceAt(this.blockPosition(), StructureTags.CATS_SPAWN_AS_BLACK, world).isValid()) { // Paper - Fix swamp hut cat generation deadlock
+ this.setVariant((Holder) BuiltInRegistries.CAT_VARIANT.getOrThrow(CatVariant.ALL_BLACK));
+ this.setPersistenceRequired();
+ }
+diff --git a/src/main/java/net/minecraft/world/level/StructureManager.java b/src/main/java/net/minecraft/world/level/StructureManager.java
+index 3d607ac74de4bc051ab68269dbab0bbd3a52dc54..03adb02297911e5a5051edac14453d7e3eeac29c 100644
+--- a/src/main/java/net/minecraft/world/level/StructureManager.java
++++ b/src/main/java/net/minecraft/world/level/StructureManager.java
+@@ -48,7 +48,12 @@ public class StructureManager {
+ }
+
+ public List<StructureStart> startsForStructure(ChunkPos pos, Predicate<Structure> predicate) {
+- Map<Structure, LongSet> map = this.level.getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences();
++ // Paper start - Fix swamp hut cat generation deadlock
++ return this.startsForStructure(pos, predicate, null);
++ }
++ public List<StructureStart> startsForStructure(ChunkPos pos, Predicate<Structure> predicate, @Nullable ServerLevelAccessor levelAccessor) {
++ Map<Structure, LongSet> map = (levelAccessor == null ? this.level : levelAccessor).getChunk(pos.x, pos.z, ChunkStatus.STRUCTURE_REFERENCES).getAllReferences();
++ // Paper end - Fix swamp hut cat generation deadlock
+ Builder<StructureStart> builder = ImmutableList.builder();
+
+ for (Entry<Structure, LongSet> entry : map.entrySet()) {
+@@ -116,10 +121,20 @@ public class StructureManager {
+ }
+
+ public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate<Holder<Structure>> predicate) {
++ // Paper start - Fix swamp hut cat generation deadlock
++ return this.getStructureWithPieceAt(pos, predicate, null);
++ }
++
++ public StructureStart getStructureWithPieceAt(BlockPos pos, TagKey<Structure> tag, @Nullable ServerLevelAccessor levelAccessor) {
++ return this.getStructureWithPieceAt(pos, structure -> structure.is(tag), levelAccessor);
++ }
++
++ public StructureStart getStructureWithPieceAt(BlockPos pos, Predicate<Holder<Structure>> predicate, @Nullable ServerLevelAccessor levelAccessor) {
++ // Paper end - Fix swamp hut cat generation deadlock
+ Registry<Structure> registry = this.registryAccess().lookupOrThrow(Registries.STRUCTURE);
+
+ for (StructureStart structureStart : this.startsForStructure(
+- new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false)
++ new ChunkPos(pos), structure -> registry.get(registry.getId(structure)).map(predicate::test).orElse(false), levelAccessor // Paper - Fix swamp hut cat generation deadlock
+ )) {
+ if (this.structureHasPieceAt(pos, structureStart)) {
+ return structureStart;
diff --git a/patches/server/0667-Don-t-allow-vehicle-movement-from-players-while-tele.patch b/patches/server/0667-Don-t-allow-vehicle-movement-from-players-while-tele.patch
new file mode 100644
index 0000000000..75b88b14da
--- /dev/null
+++ b/patches/server/0667-Don-t-allow-vehicle-movement-from-players-while-tele.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Mon, 14 Mar 2022 12:35:37 -0700
+Subject: [PATCH] Don't allow vehicle movement from players while teleporting
+
+Bring the vehicle move packet behavior in line with the
+regular player move packet.
+
+diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+index d76df6ed588b1109cdebf239884d4c9ce5de47a6..f259e32509b0ac6a62ab203911f93cac6150d71d 100644
+--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -491,6 +491,11 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause
+ } else if (!this.updateAwaitingTeleport() && this.player.hasClientLoaded()) {
+ Entity entity = this.player.getRootVehicle();
++ // Paper start - Don't allow vehicle movement from players while teleporting
++ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) {
++ return;
++ }
++ // Paper end - Don't allow vehicle movement from players while teleporting
+
+ if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) {
+ ServerLevel worldserver = this.player.serverLevel();
diff --git a/patches/server/0668-Implement-getComputedBiome-API.patch b/patches/server/0668-Implement-getComputedBiome-API.patch
new file mode 100644
index 0000000000..dab55a6218
--- /dev/null
+++ b/patches/server/0668-Implement-getComputedBiome-API.patch
@@ -0,0 +1,61 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Mon, 14 Mar 2022 22:46:05 -0700
+Subject: [PATCH] Implement getComputedBiome API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java
+index ca35e93239eea09b3d0dc6ef18f58743e633996b..a7748f4b7c5a1630937c702b3fd5fded93793d64 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftRegionAccessor.java
+@@ -77,6 +77,13 @@ public abstract class CraftRegionAccessor implements RegionAccessor {
+ return CraftBiome.minecraftHolderToBukkit(this.getHandle().getNoiseBiome(x >> 2, y >> 2, z >> 2));
+ }
+
++ // Paper start
++ @Override
++ public Biome getComputedBiome(int x, int y, int z) {
++ return CraftBiome.minecraftHolderToBukkit(this.getHandle().getBiome(new BlockPos(x, y, z)));
++ }
++ // Paper end
++
+ @Override
+ public void setBiome(Location location, Biome biome) {
+ this.setBiome(location.getBlockX(), location.getBlockY(), location.getBlockZ(), biome);
+diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
+index d5b495b5a3ca7f4411d1a700f7149042a16509f1..68fcec085334383808b2117a49220f4d8239220b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
++++ b/src/main/java/org/bukkit/craftbukkit/block/CraftBlock.java
+@@ -340,6 +340,13 @@ public class CraftBlock implements Block {
+ return this.getWorld().getBiome(this.getX(), this.getY(), this.getZ());
+ }
+
++ // Paper start
++ @Override
++ public Biome getComputedBiome() {
++ return this.getWorld().getComputedBiome(this.getX(), this.getY(), this.getZ());
++ }
++ // Paper end
++
+ @Override
+ public void setBiome(Biome bio) {
+ this.getWorld().setBiome(this.getX(), this.getY(), this.getZ(), bio);
+diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java
+index 2c7b64bd7071cb803a042152d497982d753e0b5d..a23269e3bdb83f85a1d08d5f7b54742025223ada 100644
+--- a/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java
++++ b/src/main/java/org/bukkit/craftbukkit/generator/CraftLimitedRegion.java
+@@ -166,6 +166,14 @@ public class CraftLimitedRegion extends CraftRegionAccessor implements LimitedRe
+ return super.getBiome(x, y, z);
+ }
+
++ // Paper start
++ @Override
++ public Biome getComputedBiome(int x, int y, int z) {
++ Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z);
++ return super.getComputedBiome(x, y, z);
++ }
++ // Paper end
++
+ @Override
+ public void setBiome(int x, int y, int z, Holder<net.minecraft.world.level.biome.Biome> biomeBase) {
+ Preconditions.checkArgument(this.isInRegion(x, y, z), "Coordinates %s, %s, %s are not in the region", x, y, z);
diff --git a/patches/server/0669-Make-some-itemstacks-nonnull.patch b/patches/server/0669-Make-some-itemstacks-nonnull.patch
new file mode 100644
index 0000000000..20944d9088
--- /dev/null
+++ b/patches/server/0669-Make-some-itemstacks-nonnull.patch
@@ -0,0 +1,28 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Tue, 15 Mar 2022 01:38:15 -0700
+Subject: [PATCH] Make some itemstacks nonnull
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java
+index 107fa1d4bd977d17dc062da280dda46eb3c5f81c..e62baea16df017f1e394e3c706157e158066eb93 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java
+@@ -155,13 +155,13 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i
+ case OFF_HAND:
+ return this.getItemInOffHand();
+ case FEET:
+- return this.getBoots();
++ return java.util.Objects.requireNonNullElseGet(this.getBoots(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull
+ case LEGS:
+- return this.getLeggings();
++ return java.util.Objects.requireNonNullElseGet(this.getLeggings(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull
+ case CHEST:
+- return this.getChestplate();
++ return java.util.Objects.requireNonNullElseGet(this.getChestplate(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull
+ case HEAD:
+- return this.getHelmet();
++ return java.util.Objects.requireNonNullElseGet(this.getHelmet(), () -> new ItemStack(org.bukkit.Material.AIR)); // Paper - make nonnull
+ default:
+ throw new IllegalArgumentException("Could not get slot " + slot + " - not a valid slot for PlayerInventory");
+ }
diff --git a/patches/server/0670-Implement-enchantWithLevels-API.patch b/patches/server/0670-Implement-enchantWithLevels-API.patch
new file mode 100644
index 0000000000..008d6aaf29
--- /dev/null
+++ b/patches/server/0670-Implement-enchantWithLevels-API.patch
@@ -0,0 +1,58 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jason Penilla <[email protected]>
+Date: Wed, 16 Mar 2022 20:35:21 -0700
+Subject: [PATCH] Implement enchantWithLevels API
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
+index a04840b77e2d84e754c9cfa79bc593608bc22c90..6d228d643342588877e8385acdc202de57d455f1 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
+@@ -307,4 +307,47 @@ public final class CraftItemFactory implements ItemFactory {
+ return eggItem == null ? null : new net.minecraft.world.item.ItemStack(eggItem).asBukkitMirror();
+ }
+ // Paper end - old getSpawnEgg API
++ // Paper start - enchantWithLevels API
++ @Override
++ public ItemStack enchantWithLevels(ItemStack itemStack, int levels, boolean allowTreasure, java.util.Random random) {
++ return enchantWithLevels(
++ itemStack,
++ levels,
++ allowTreasure
++ ? Optional.empty()
++ // While IN_ENCHANTING_TABLE is not logically the same as all but TREASURE, the tag is defined as
++ // NON_TREASURE, which does contain all enchantments not in the treasure tag.
++ // Additionally, the allowTreasure boolean is more intended to configure this method to behave like
++ // an enchanting table.
++ : net.minecraft.server.MinecraftServer.getServer().registryAccess().lookupOrThrow(Registries.ENCHANTMENT).get(EnchantmentTags.IN_ENCHANTING_TABLE),
++ random
++ );
++ }
++
++ @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
++ private ItemStack enchantWithLevels(
++ ItemStack itemStack,
++ int levels,
++ Optional<? extends net.minecraft.core.HolderSet<net.minecraft.world.item.enchantment.Enchantment>> possibleEnchantments,
++ java.util.Random random
++ ) {
++ Preconditions.checkArgument(itemStack != null, "Argument 'itemStack' must not be null");
++ Preconditions.checkArgument(!itemStack.isEmpty(), "Argument 'itemStack' cannot be empty");
++ Preconditions.checkArgument(levels > 0 && levels <= 30, "Argument 'levels' must be in range [1, 30] (attempted " + levels + ")");
++ Preconditions.checkArgument(random != null, "Argument 'random' must not be null");
++ final net.minecraft.world.item.ItemStack internalStack = CraftItemStack.asNMSCopy(itemStack);
++ if (internalStack.isEnchanted()) {
++ internalStack.set(net.minecraft.core.component.DataComponents.ENCHANTMENTS, net.minecraft.world.item.enchantment.ItemEnchantments.EMPTY);
++ }
++ final net.minecraft.core.RegistryAccess registryAccess = net.minecraft.server.MinecraftServer.getServer().registryAccess();
++ final net.minecraft.world.item.ItemStack enchanted = net.minecraft.world.item.enchantment.EnchantmentHelper.enchantItem(
++ new org.bukkit.craftbukkit.util.RandomSourceWrapper(random),
++ internalStack,
++ levels,
++ registryAccess,
++ possibleEnchantments
++ );
++ return CraftItemStack.asCraftMirror(enchanted);
++ }
++ // Paper end - enchantWithLevels API
+ }
diff --git a/patches/server/0671-Fix-saving-in-unloadWorld.patch b/patches/server/0671-Fix-saving-in-unloadWorld.patch
new file mode 100644
index 0000000000..540896076f
--- /dev/null
+++ b/patches/server/0671-Fix-saving-in-unloadWorld.patch
@@ -0,0 +1,20 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Philip Kelley <[email protected]>
+Date: Wed, 16 Mar 2022 12:05:59 +0000
+Subject: [PATCH] Fix saving in unloadWorld
+
+Change savingDisabled to false to ensure ServerLevel's saving logic gets called when unloadWorld is called with save = true
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 82e1c4713e043c4903b1f0154609da4558f90aef..430b35c35c7aa17d590031515063f2d24eeffe5c 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -1385,7 +1385,7 @@ public final class CraftServer implements Server {
+
+ try {
+ if (save) {
+- handle.save(null, true, true);
++ handle.save(null, true, false); // Paper - Fix saving in unloadWorld
+ }
+
+ handle.getChunkSource().close(save);
diff --git a/patches/server/0672-Buffer-OOB-setBlock-calls.patch b/patches/server/0672-Buffer-OOB-setBlock-calls.patch
new file mode 100644
index 0000000000..1b7ba291c4
--- /dev/null
+++ b/patches/server/0672-Buffer-OOB-setBlock-calls.patch
@@ -0,0 +1,42 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Shane Freeder <[email protected]>
+Date: Sat, 19 Mar 2022 12:12:22 +0000
+Subject: [PATCH] Buffer OOB setBlock calls
+
+lets debug mode throw a trace in order to potentially see where
+such calls are cascading from easier, but, generally, if you see one setBlock
+call, you're gonna see more, and this just potentially causes a flood of logs
+which can cause issues for slower terminals, etc.
+
+We can limit the flood by just allowing one for a single gen region,
+we'll also only gen a trace for the first one, I see no real pressing need
+to generate more, given that that would *massively* negate this patch otherwise
+
+diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
+index f1725ef766c35aa623ace58fe8bf31fc9b2bb6b3..5bf438bb58833c1df3620e82d3d2b90207366372 100644
+--- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java
++++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
+@@ -284,6 +284,7 @@ public class WorldGenRegion implements WorldGenLevel {
+ }
+ }
+
++ private boolean hasSetFarWarned = false; // Paper - Buffer OOB setBlock calls
+ @Override
+ public boolean ensureCanWrite(BlockPos pos) {
+ int i = SectionPos.blockToSectionCoord(pos.getX());
+@@ -303,7 +304,15 @@ public class WorldGenRegion implements WorldGenLevel {
+
+ return true;
+ } else {
++ // Paper start - Buffer OOB setBlock calls
++ if (!hasSetFarWarned) {
+ Util.logAndPauseIfInIde("Detected setBlock in a far chunk [" + i + ", " + j + "], pos: " + String.valueOf(pos) + ", status: " + String.valueOf(this.generatingStep.targetStatus()) + (this.currentlyGenerating == null ? "" : ", currently generating: " + (String) this.currentlyGenerating.get()));
++ hasSetFarWarned = true;
++ if (this.getServer() != null && this.getServer().isDebugging()) {
++ io.papermc.paper.util.TraceUtil.dumpTraceForThread("far setBlock call");
++ }
++ }
++ // Paper end - Buffer OOB setBlock calls
+ return false;
+ }
+ }
diff --git a/patches/server/0673-Add-TameableDeathMessageEvent.patch b/patches/server/0673-Add-TameableDeathMessageEvent.patch
new file mode 100644
index 0000000000..85016be93d
--- /dev/null
+++ b/patches/server/0673-Add-TameableDeathMessageEvent.patch
@@ -0,0 +1,24 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Mon, 21 Jun 2021 21:24:45 -0400
+Subject: [PATCH] Add TameableDeathMessageEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/TamableAnimal.java b/src/main/java/net/minecraft/world/entity/TamableAnimal.java
+index 45a340fddcec48dfb796f4515a500452adb30c59..1c195c928fb5c165981665393424a64004331c93 100644
+--- a/src/main/java/net/minecraft/world/entity/TamableAnimal.java
++++ b/src/main/java/net/minecraft/world/entity/TamableAnimal.java
+@@ -257,7 +257,12 @@ public abstract class TamableAnimal extends Animal implements OwnableEntity {
+ if (entityliving instanceof ServerPlayer) {
+ ServerPlayer entityplayer = (ServerPlayer) entityliving;
+
+- entityplayer.sendSystemMessage(this.getCombatTracker().getDeathMessage());
++ // Paper start - Add TameableDeathMessageEvent
++ io.papermc.paper.event.entity.TameableDeathMessageEvent event = new io.papermc.paper.event.entity.TameableDeathMessageEvent((org.bukkit.entity.Tameable) getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(this.getCombatTracker().getDeathMessage()));
++ if (event.callEvent()) {
++ entityplayer.sendSystemMessage(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.deathMessage()));
++ }
++ // Paper end - Add TameableDeathMessageEvent
+ }
+ }
+ }
diff --git a/patches/server/0674-Fix-new-block-data-for-EntityChangeBlockEvent.patch b/patches/server/0674-Fix-new-block-data-for-EntityChangeBlockEvent.patch
new file mode 100644
index 0000000000..fd2ea22a5e
--- /dev/null
+++ b/patches/server/0674-Fix-new-block-data-for-EntityChangeBlockEvent.patch
@@ -0,0 +1,215 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: SoSeDiK <[email protected]>
+Date: Mon, 21 Mar 2022 20:00:53 +0200
+Subject: [PATCH] Fix new block data for EntityChangeBlockEvent
+
+Also standardizes how to handle EntityChangeBlockEvent before a removeBlock or destroyBlock
+call. Always use 'state.getFluidState().createLegacyBlock()' to get the new state instead of
+just using the 'air' state.
+
+Also fixes the new block data for EntityBreakDoorEvent (a sub-event from
+EntityChangeBlockEvent)
+
+Co-authored-by: Lulu13022002 <[email protected]>
+Co-authored-by: Jake Potrebic <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
+index 6a7052cd6ec88ed4aaea2fef0ebc750060d1dec0..2ade08d1466660ee1787fa97908002ef56389712 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
++++ b/src/main/java/net/minecraft/world/entity/ai/behavior/HarvestFarmland.java
+@@ -108,7 +108,7 @@ public class HarvestFarmland extends Behavior<Villager> {
+ Block block1 = world.getBlockState(this.aboveFarmlandPos.below()).getBlock();
+
+ if (block instanceof CropBlock && ((CropBlock) block).isMaxAge(iblockdata)) {
+- if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, Blocks.AIR.defaultBlockState())) { // CraftBukkit
++ if (CraftEventFactory.callEntityChangeBlockEvent(entity, this.aboveFarmlandPos, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit // Paper - fix wrong block state
+ world.destroyBlock(this.aboveFarmlandPos, true, entity);
+ } // CraftBukkit
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
+index e2aa6d46375cbc4f4b694888c4f9750eb26e4940..6827426e6e9706909265f84bf97b5fa7105a7fea 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
++++ b/src/main/java/net/minecraft/world/entity/ai/goal/BreakDoorGoal.java
+@@ -73,7 +73,7 @@ public class BreakDoorGoal extends DoorInteractGoal {
+
+ if (this.breakTime == this.getDoorBreakTime() && this.isValidDifficulty(this.mob.level().getDifficulty())) {
+ // CraftBukkit start
+- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos).isCancelled()) {
++ if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityBreakDoorEvent(this.mob, this.doorPos, this.mob.level().getFluidState(this.doorPos).createLegacyBlock()).isCancelled()) { // Paper - fix wrong block state
+ this.start();
+ return;
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
+index dc3602002e60d64cbbe9504c22282a9e65da2c9d..9e6f946e6d2878aa3fa8abe0f6fa4770d18676d3 100644
+--- a/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
++++ b/src/main/java/net/minecraft/world/entity/ai/goal/EatBlockGoal.java
+@@ -67,8 +67,9 @@ public class EatBlockGoal extends Goal {
+ if (this.eatAnimationTick == this.adjustedTickDelay(4)) {
+ BlockPos blockposition = this.mob.blockPosition();
+
+- if (EatBlockGoal.IS_TALL_GRASS.test(this.level.getBlockState(blockposition))) {
+- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, Blocks.AIR.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
++ final BlockState blockState = this.level.getBlockState(blockposition); // Paper - fix wrong block state
++ if (EatBlockGoal.IS_TALL_GRASS.test(blockState)) { // Paper - fix wrong block state
++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition, blockState.getFluidState().createLegacyBlock(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - fix wrong block state
+ this.level.destroyBlock(blockposition, false);
+ }
+
+@@ -77,7 +78,7 @@ public class EatBlockGoal extends Goal {
+ BlockPos blockposition1 = blockposition.below();
+
+ if (this.level.getBlockState(blockposition1).is(Blocks.GRASS_BLOCK)) {
+- if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.AIR.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit
++ if (CraftEventFactory.callEntityChangeBlockEvent(this.mob, blockposition1, Blocks.DIRT.defaultBlockState(), !getServerLevel(this.level).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING))) { // CraftBukkit // Paper - Fix wrong block state
+ this.level.levelEvent(2001, blockposition1, Block.getId(Blocks.GRASS_BLOCK.defaultBlockState()));
+ this.level.setBlock(blockposition1, Blocks.DIRT.defaultBlockState(), 2);
+ }
+diff --git a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java
+index cfda434358a2e14ebd6cc10609c599f6d65bb6ef..53d60d62686f9b6bc98b6b25e4315b848600a99d 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/Rabbit.java
++++ b/src/main/java/net/minecraft/world/entity/animal/Rabbit.java
+@@ -580,7 +580,7 @@ public class Rabbit extends Animal implements VariantHolder<Rabbit.Variant> {
+
+ if (i == 0) {
+ // CraftBukkit start
+- if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, Blocks.AIR.defaultBlockState())) {
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.rabbit, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
+ return;
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
+index 8415f7e1f8c391961dc5b0669da1ab4f8ae4950d..fa8c7a7f71621437bec2ce69c3ad24b495ceed1d 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
++++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java
+@@ -374,7 +374,7 @@ public class WitherBoss extends Monster implements RangedAttackMob {
+
+ if (WitherBoss.canDestroy(iblockdata)) {
+ // CraftBukkit start
+- if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, Blocks.AIR.defaultBlockState())) {
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
+ continue;
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
+index 4b798695af365dc97cbbbd09f370b8fc425f9ed6..2a394381a4ad46359359ba402b65c62b331480b4 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
++++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java
+@@ -551,7 +551,7 @@ public class EnderMan extends Monster implements NeutralMob {
+ boolean flag = movingobjectpositionblock.getBlockPos().equals(blockposition);
+
+ if (iblockdata.is(BlockTags.ENDERMAN_HOLDABLE) && flag) {
+- if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, Blocks.AIR.defaultBlockState())) { // CraftBukkit - Place event
++ if (CraftEventFactory.callEntityChangeBlockEvent(this.enderman, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // CraftBukkit - Place event // Paper - fix wrong block state
+ world.removeBlock(blockposition, false);
+ world.gameEvent((Holder) GameEvent.BLOCK_DESTROY, blockposition, GameEvent.Context.of(this.enderman, iblockdata));
+ this.enderman.setCarriedBlock(iblockdata.getBlock().defaultBlockState());
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Ravager.java b/src/main/java/net/minecraft/world/entity/monster/Ravager.java
+index 772476d44ee72aed1ba35d10fe51f0ffda34d3f8..cfc28828a5b81563a826ae6045553e7350f67986 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java
+@@ -162,7 +162,7 @@ public class Ravager extends Raider {
+
+ if (block instanceof LeavesBlock) {
+ // CraftBukkit start
+- if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) {
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this, blockposition, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
+ continue;
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java
+index 52d8ea3e40cdb01eab428f5d3d945c0c9f6088ce..ff65cb8ea5233f2dd159f42ad53bc9d300cd604f 100644
+--- a/src/main/java/net/minecraft/world/entity/monster/Silverfish.java
++++ b/src/main/java/net/minecraft/world/entity/monster/Silverfish.java
+@@ -165,7 +165,8 @@ public class Silverfish extends Monster {
+
+ if (block instanceof InfestedBlock) {
+ // CraftBukkit start
+- if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState())) {
++ BlockState afterState = getServerLevel(world).getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING) ? iblockdata.getFluidState().createLegacyBlock() : ((InfestedBlock) block).hostStateByInfested(world.getBlockState(blockposition1)); // Paper - fix wrong block state
++ if (!CraftEventFactory.callEntityChangeBlockEvent(this.silverfish, blockposition1, afterState)) { // Paper - fix wrong block state
+ continue;
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
+index a486466040a646b8a5a5ff2430cdd25b95b7e20f..e78eef3b6fbcd657f9dd180df4cb2eeb55d0814f 100644
+--- a/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
++++ b/src/main/java/net/minecraft/world/entity/projectile/ThrownPotion.java
+@@ -294,7 +294,7 @@ public class ThrownPotion extends ThrowableItemProjectile {
+
+ if (iblockdata.is(BlockTags.FIRE)) {
+ // CraftBukkit start
+- if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, Blocks.AIR.defaultBlockState())) {
++ if (CraftEventFactory.callEntityChangeBlockEvent(this, pos, iblockdata.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
+ this.level().destroyBlock(pos, false, this);
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java b/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java
+index fae574f8617bada96469a2eb1349eaa061f0450f..6d0d13e70a82c4db7848e1007f5b6d670dd5acad 100644
+--- a/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/ChorusFlowerBlock.java
+@@ -286,7 +286,7 @@ public class ChorusFlowerBlock extends Block {
+ if (world instanceof ServerLevel worldserver) {
+ if (projectile.mayInteract(worldserver, blockposition) && projectile.mayBreak(worldserver)) {
+ // CraftBukkit
+- if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) {
++ if (!CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
+ return;
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java
+index 48503f5ba8bccb42ae3b5324a5f65f6c23a8e479..bd38a0a73d543a85bb5c6d50219f5438ce194df3 100644
+--- a/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/PointedDripstoneBlock.java
+@@ -137,7 +137,7 @@ public class PointedDripstoneBlock extends Block implements Fallable, SimpleWate
+
+ if (projectile.mayInteract(worldserver, blockposition) && projectile.mayBreak(worldserver) && projectile instanceof ThrownTrident && projectile.getDeltaMovement().length() > 0.6D) {
+ // CraftBukkit start
+- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState())) {
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
+ return;
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/level/block/TntBlock.java b/src/main/java/net/minecraft/world/level/block/TntBlock.java
+index d256b0f3998028709334dd6c394d184f2c36efce..2cbe2053dd5d0bcdcd89de69762c77b400b8697a 100644
+--- a/src/main/java/net/minecraft/world/level/block/TntBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/TntBlock.java
+@@ -158,7 +158,7 @@ public class TntBlock extends Block {
+
+ if (projectile.isOnFire() && projectile.mayInteract(worldserver, blockposition)) {
+ // CraftBukkit start
+- if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, Blocks.AIR.defaultBlockState()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) {
++ if (!org.bukkit.craftbukkit.event.CraftEventFactory.callEntityChangeBlockEvent(projectile, blockposition, state.getFluidState().createLegacyBlock()) || !CraftEventFactory.callTNTPrimeEvent(world, blockposition, PrimeCause.PROJECTILE, projectile, null)) { // Paper - fix wrong block state
+ return;
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java
+index 674d710ff88db5eced9e017284d1b7ec7a4fe7cd..72320c6099a4b26235bab68570e7b7efad84740f 100644
+--- a/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/WaterlilyBlock.java
+@@ -37,7 +37,7 @@ public class WaterlilyBlock extends BushBlock {
+ super.entityInside(state, world, pos, entity);
+ if (world instanceof ServerLevel && entity instanceof AbstractBoat) {
+ // CraftBukkit start
+- if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.AIR.defaultBlockState())) {
++ if (!CraftEventFactory.callEntityChangeBlockEvent(entity, pos, state.getFluidState().createLegacyBlock())) { // Paper - fix wrong block state
+ return;
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+index ca094e393de32b64db59c9fe906433761d70d29b..fcc9fe8f5f57e9f6c28d762aa6585942e2b2698b 100644
+--- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
++++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
+@@ -1378,11 +1378,11 @@ public class CraftEventFactory {
+ return event;
+ }
+
+- public static EntityBreakDoorEvent callEntityBreakDoorEvent(Entity entity, BlockPos pos) {
++ public static EntityBreakDoorEvent callEntityBreakDoorEvent(Entity entity, BlockPos pos, net.minecraft.world.level.block.state.BlockState newState) { // Paper
+ org.bukkit.entity.Entity entity1 = entity.getBukkitEntity();
+ Block block = CraftBlock.at(entity.level(), pos);
+
+- EntityBreakDoorEvent event = new EntityBreakDoorEvent((LivingEntity) entity1, block);
++ EntityBreakDoorEvent event = new EntityBreakDoorEvent((LivingEntity) entity1, block, newState.createCraftBlockData()); // Paper
+ entity1.getServer().getPluginManager().callEvent(event);
+
+ return event;
diff --git a/patches/server/0675-fix-player-loottables-running-when-mob-loot-gamerule.patch b/patches/server/0675-fix-player-loottables-running-when-mob-loot-gamerule.patch
new file mode 100644
index 0000000000..2bc0c5993d
--- /dev/null
+++ b/patches/server/0675-fix-player-loottables-running-when-mob-loot-gamerule.patch
@@ -0,0 +1,25 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Tue, 22 Mar 2022 09:50:40 -0700
+Subject: [PATCH] fix player loottables running when mob loot gamerule is false
+
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index e2a8e0291a5c0c00d7d6135a0d34fb8bab7b4cd5..15158bec7e2ca90b45347ba09cf17f25e919f9cf 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -1215,12 +1215,14 @@ public class ServerPlayer extends net.minecraft.world.entity.player.Player {
+ }
+ }
+ }
++ if (this.shouldDropLoot() && this.serverLevel().getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { // Paper - fix player loottables running when mob loot gamerule is false
+ // SPIGOT-5071: manually add player loot tables (SPIGOT-5195 - ignores keepInventory rule)
+ this.dropFromLootTable(this.serverLevel(), damageSource, this.lastHurtByPlayerTime > 0);
+ this.dropCustomDeathLoot(this.serverLevel(), damageSource, flag);
+
+ loot.addAll(this.drops);
+ this.drops.clear(); // SPIGOT-5188: make sure to clear
++ } // Paper - fix player loottables running when mob loot gamerule is false
+
+ Component defaultMessage = this.getCombatTracker().getDeathMessage();
+
diff --git a/patches/server/0676-Ensure-entity-passenger-world-matches-ridden-entity.patch b/patches/server/0676-Ensure-entity-passenger-world-matches-ridden-entity.patch
new file mode 100644
index 0000000000..df6388cd5f
--- /dev/null
+++ b/patches/server/0676-Ensure-entity-passenger-world-matches-ridden-entity.patch
@@ -0,0 +1,20 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Thu, 31 Mar 2022 05:11:37 -0700
+Subject: [PATCH] Ensure entity passenger world matches ridden entity
+
+Bad plugins doing this would cause some obvious problems...
+
+diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
+index 4784bfb23a7e0004287f89213d50bcfaaaed9e38..f2572fec8989911803cbcf1110e670b005ae29b0 100644
+--- a/src/main/java/net/minecraft/world/entity/Entity.java
++++ b/src/main/java/net/minecraft/world/entity/Entity.java
+@@ -2833,7 +2833,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
+ }
+
+ public boolean startRiding(Entity entity, boolean force) {
+- if (entity == this.vehicle) {
++ if (entity == this.vehicle || entity.level != this.level) { // Paper - Ensure entity passenger world matches ridden entity (bad plugins)
+ return false;
+ } else if (!entity.couldAcceptPassenger()) {
+ return false;
diff --git a/patches/server/0677-Cache-resource-keys-and-optimize-reference-Holder-ta.patch b/patches/server/0677-Cache-resource-keys-and-optimize-reference-Holder-ta.patch
new file mode 100644
index 0000000000..7356aca21a
--- /dev/null
+++ b/patches/server/0677-Cache-resource-keys-and-optimize-reference-Holder-ta.patch
@@ -0,0 +1,38 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sun, 20 Mar 2022 22:06:47 -0700
+Subject: [PATCH] Cache resource keys and optimize reference Holder tags set
+
+TagKeys are always interned, so we can use a reference hash set for them
+
+diff --git a/src/main/java/net/minecraft/core/Holder.java b/src/main/java/net/minecraft/core/Holder.java
+index 94671ea227b59a8f820425c401712e6aeb8b2b10..e91c4e26c25980645941ca8fbdcc3a9d02e31063 100644
+--- a/src/main/java/net/minecraft/core/Holder.java
++++ b/src/main/java/net/minecraft/core/Holder.java
+@@ -230,7 +230,7 @@ public interface Holder<T> {
+ }
+
+ void bindTags(Collection<TagKey<T>> tags) {
+- this.tags = Set.copyOf(tags);
++ this.tags = java.util.Collections.unmodifiableSet(new it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet<>(tags)); // Paper
+ }
+
+ @Override
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java
+index 6cf8af0c85231de9955282d4abaa0607ec9a195c..d230cbc26f61d8ac5880825aca4dfab197c20401 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntityType.java
+@@ -25,11 +25,11 @@ public class CraftEntityType {
+ return bukkit;
+ }
+
++ private static final java.util.Map<EntityType, net.minecraft.resources.ResourceKey<net.minecraft.world.entity.EntityType<?>>> KEY_CACHE = java.util.Collections.synchronizedMap(new java.util.EnumMap<>(EntityType.class)); // Paper
+ public static net.minecraft.world.entity.EntityType<?> bukkitToMinecraft(EntityType bukkit) {
+ Preconditions.checkArgument(bukkit != null);
+-
+ return CraftRegistry.getMinecraftRegistry(Registries.ENTITY_TYPE)
+- .getOptional(CraftNamespacedKey.toMinecraft(bukkit.getKey())).orElseThrow();
++ .getOptional(KEY_CACHE.computeIfAbsent(bukkit, type -> net.minecraft.resources.ResourceKey.create(Registries.ENTITY_TYPE, CraftNamespacedKey.toMinecraft(type.getKey())))).orElseThrow();
+ }
+
+ public static Holder<net.minecraft.world.entity.EntityType<?>> bukkitToMinecraftHolder(EntityType bukkit) {
diff --git a/patches/server/0678-Allow-changing-the-EnderDragon-podium.patch b/patches/server/0678-Allow-changing-the-EnderDragon-podium.patch
new file mode 100644
index 0000000000..0676f66fd1
--- /dev/null
+++ b/patches/server/0678-Allow-changing-the-EnderDragon-podium.patch
@@ -0,0 +1,142 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Doc <[email protected]>
+Date: Sun, 3 Apr 2022 11:31:42 -0400
+Subject: [PATCH] Allow changing the EnderDragon podium
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+index f5aee15f80e226c8d59e66b1a53f529e2fb524a5..037b261a40a800f6d5e9ecd22e230d030b797958 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java
+@@ -104,6 +104,10 @@ public class EnderDragon extends Mob implements Enemy {
+ private final int[] nodeAdjacency;
+ private final BinaryHeap openSet;
+ private final Explosion explosionSource; // CraftBukkit - reusable source for CraftTNTPrimed.getSource()
++ // Paper start - Allow changing the EnderDragon podium
++ @Nullable
++ private BlockPos podium;
++ // Paper end - Allow changing the EnderDragon podium
+
+ public EnderDragon(EntityType<? extends EnderDragon> entitytypes, Level world) {
+ super(EntityType.ENDER_DRAGON, world);
+@@ -143,6 +147,19 @@ public class EnderDragon extends Mob implements Enemy {
+ return Mob.createMobAttributes().add(Attributes.MAX_HEALTH, 200.0D);
+ }
+
++ // Paper start - Allow changing the EnderDragon podium
++ public BlockPos getPodium() {
++ if (this.podium == null) {
++ return EndPodiumFeature.getLocation(this.getFightOrigin());
++ }
++ return this.podium;
++ }
++
++ public void setPodium(@Nullable BlockPos blockPos) {
++ this.podium = blockPos;
++ }
++ // Paper end - Allow changing the EnderDragon podium
++
+ @Override
+ public boolean isFlapping() {
+ float f = Mth.cos(this.flapTime * 6.2831855F);
+@@ -1002,7 +1019,7 @@ public class EnderDragon extends Mob implements Enemy {
+ vec3d = this.getViewVector(tickDelta);
+ }
+ } else {
+- BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.fightOrigin));
++ BlockPos blockposition = this.level().getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.getPodium()); // Paper - Allow changing the EnderDragon podium
+
+ f1 = Math.max((float) Math.sqrt(blockposition.distToCenterSqr(this.position())) / 4.0F, 1.0F);
+ float f3 = 6.0F / f1;
+diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java
+index a897c994423d7d624df6ff3a67789cc2436f0417..29f4acc2943ce009088c61bb32aed330f6e2c051 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java
++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonDeathPhase.java
+@@ -42,7 +42,7 @@ public class DragonDeathPhase extends AbstractDragonPhaseInstance {
+ public void doServerTick(ServerLevel world) {
+ this.time++;
+ if (this.targetLocation == null) {
+- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
+ this.targetLocation = Vec3.atBottomCenterOf(blockPos);
+ }
+
+diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java
+index 2db996f3528c65f5d719cbcfb8ae587ff59c14c1..8e39cf181993ff284a2b0429577de0c307f3ef16 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java
++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonHoldingPatternPhase.java
+@@ -53,7 +53,7 @@ public class DragonHoldingPatternPhase extends AbstractDragonPhaseInstance {
+
+ private void findNewTarget(ServerLevel world) {
+ if (this.currentPath != null && this.currentPath.isDone()) {
+- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
+ int i = this.dragon.getDragonFight() == null ? 0 : this.dragon.getDragonFight().getCrystalsAlive();
+ if (this.dragon.getRandom().nextInt(i + 3) == 0) {
+ this.dragon.getPhaseManager().setPhase(EnderDragonPhase.LANDING_APPROACH);
+diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java
+index 4c28d3c3d601b5316d79c8474d106800abc8e95b..1c4250cc61b770707ad25c0e93caeacbcb0a80b0 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java
++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingApproachPhase.java
+@@ -52,7 +52,7 @@ public class DragonLandingApproachPhase extends AbstractDragonPhaseInstance {
+ private void findNewTarget(ServerLevel world) {
+ if (this.currentPath == null || this.currentPath.isDone()) {
+ int i = this.dragon.findClosestNode();
+- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
+ Player player = world.getNearestPlayer(NEAR_EGG_TARGETING, this.dragon, (double)blockPos.getX(), (double)blockPos.getY(), (double)blockPos.getZ());
+ int j;
+ if (player != null) {
+diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java
+index 789df0e05d0cf0df0f66bffc98f587a31770ebea..cfcd3f91517832d26c1177c3715cfa8ebe675193 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java
++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonLandingPhase.java
+@@ -42,7 +42,7 @@ public class DragonLandingPhase extends AbstractDragonPhaseInstance {
+ public void doServerTick(ServerLevel world) {
+ if (this.targetLocation == null) {
+ this.targetLocation = Vec3.atBottomCenterOf(
+- world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()))
++ world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()) // Paper - Allow changing the EnderDragon podium
+ );
+ }
+
+diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java
+index f942ea2ad87d4ae12921578f7b869f064c8db7b0..5c5b03f612621ec4efd92b78601032b84e6f3c28 100644
+--- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java
++++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonTakeoffPhase.java
+@@ -24,7 +24,7 @@ public class DragonTakeoffPhase extends AbstractDragonPhaseInstance {
+ @Override
+ public void doServerTick(ServerLevel world) {
+ if (!this.firstTick && this.currentPath != null) {
+- BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, EndPodiumFeature.getLocation(this.dragon.getFightOrigin()));
++ BlockPos blockPos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, this.dragon.getPodium()); // Paper - Allow changing the EnderDragon podium
+ if (!blockPos.closerToCenterThan(this.dragon.position(), 10.0)) {
+ this.dragon.getPhaseManager().setPhase(EnderDragonPhase.HOLDING_PATTERN);
+ }
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java
+index 25b3d889a1742c347e60725df8d6f6c1cee264c7..7b7b89e67d53ed70efae714192c5fa32977f3d9c 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEnderDragon.java
+@@ -73,4 +73,22 @@ public class CraftEnderDragon extends CraftMob implements EnderDragon, CraftEnem
+ public int getDeathAnimationTicks() {
+ return this.getHandle().dragonDeathTime;
+ }
++
++ // Paper start - Allow changing the EnderDragon podium
++ @Override
++ public org.bukkit.Location getPodium() {
++ net.minecraft.core.BlockPos blockPosOrigin = this.getHandle().getPodium();
++ return new org.bukkit.Location(getWorld(), blockPosOrigin.getX(), blockPosOrigin.getY(), blockPosOrigin.getZ());
++ }
++
++ @Override
++ public void setPodium(org.bukkit.Location location) {
++ if (location == null) {
++ this.getHandle().setPodium(null);
++ } else {
++ org.apache.commons.lang.Validate.isTrue(location.getWorld() == null || location.getWorld().equals(getWorld()), "You cannot set a podium in a different world to where the dragon is");
++ this.getHandle().setPodium(io.papermc.paper.util.MCUtil.toBlockPos(location));
++ }
++ }
++ // Paper end - Allow changing the EnderDragon podium
+ }
diff --git a/patches/server/0679-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch b/patches/server/0679-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch
new file mode 100644
index 0000000000..f885dbe8b4
--- /dev/null
+++ b/patches/server/0679-Fix-NBT-pieces-overriding-a-block-entity-during-worl.patch
@@ -0,0 +1,40 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: etil2jz <[email protected]>
+Date: Sat, 2 Apr 2022 23:29:24 +0200
+Subject: [PATCH] Fix NBT pieces overriding a block entity during worldgen
+ deadlock
+
+By checking if the world passed into StructureTemplate's placeInWorld
+is not a WorldGenRegion, we can bypass the deadlock entirely.
+See https://bugs.mojang.com/browse/MC-246262
+
+diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
+index b120949667ae0169a667b329b3cabbd79a0a5bda..734f511d197bc6bf2b02588069eb02c0224781f5 100644
+--- a/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
++++ b/src/main/java/net/minecraft/world/level/levelgen/structure/templatesystem/StructureTemplate.java
+@@ -303,7 +303,11 @@ public class StructureTemplate {
+
+ if (definedstructure_blockinfo.nbt != null) {
+ tileentity = world.getBlockEntity(blockposition2);
+- Clearable.tryClear(tileentity);
++ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock
++ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) {
++ Clearable.tryClear(tileentity);
++ }
++ // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock
+ world.setBlock(blockposition2, Blocks.BARRIER.defaultBlockState(), 20);
+ }
+ // CraftBukkit start
+@@ -430,7 +434,11 @@ public class StructureTemplate {
+ if (pair1.getSecond() != null) {
+ tileentity = world.getBlockEntity(blockposition6);
+ if (tileentity != null) {
+- tileentity.setChanged();
++ // Paper start - Fix NBT pieces overriding a block entity during worldgen deadlock
++ if (!(world instanceof net.minecraft.world.level.WorldGenLevel)) {
++ tileentity.setChanged();
++ }
++ // Paper end - Fix NBT pieces overriding a block entity during worldgen deadlock
+ }
+ }
+ }
diff --git a/patches/server/0680-Use-username-instead-of-display-name-in-PlayerList-g.patch b/patches/server/0680-Use-username-instead-of-display-name-in-PlayerList-g.patch
new file mode 100644
index 0000000000..5407679d61
--- /dev/null
+++ b/patches/server/0680-Use-username-instead-of-display-name-in-PlayerList-g.patch
@@ -0,0 +1,20 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Doc <[email protected]>
+Date: Fri, 15 Apr 2022 17:40:30 -0400
+Subject: [PATCH] Use username instead of display name in
+ PlayerList#getPlayerStats
+
+
+diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
+index c8bb797a322220647d5839cc5d20b3ac7c01b8ba..1456945e8d6e82c59bf09150bfe24bd1ae14a8c3 100644
+--- a/src/main/java/net/minecraft/server/players/PlayerList.java
++++ b/src/main/java/net/minecraft/server/players/PlayerList.java
+@@ -1352,7 +1352,7 @@ public abstract class PlayerList {
+ // CraftBukkit start
+ public ServerStatsCounter getPlayerStats(ServerPlayer entityhuman) {
+ ServerStatsCounter serverstatisticmanager = entityhuman.getStats();
+- return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getDisplayName().getString()) : serverstatisticmanager;
++ return serverstatisticmanager == null ? this.getPlayerStats(entityhuman.getUUID(), entityhuman.getGameProfile().getName()) : serverstatisticmanager; // Paper - use username and not display name
+ }
+
+ public ServerStatsCounter getPlayerStats(UUID uuid, String displayName) {
diff --git a/patches/server/0681-Expand-PlayerItemDamageEvent.patch b/patches/server/0681-Expand-PlayerItemDamageEvent.patch
new file mode 100644
index 0000000000..85a05db466
--- /dev/null
+++ b/patches/server/0681-Expand-PlayerItemDamageEvent.patch
@@ -0,0 +1,23 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: HexedHero <[email protected]>
+Date: Sun, 10 Apr 2022 06:26:32 +0100
+Subject: [PATCH] Expand PlayerItemDamageEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java
+index 18f84d54ec72debec652adb22067e11aa058b238..679112d1622744680b7d3c7d296acafac019d00a 100644
+--- a/src/main/java/net/minecraft/world/item/ItemStack.java
++++ b/src/main/java/net/minecraft/world/item/ItemStack.java
+@@ -698,10 +698,11 @@ public final class ItemStack implements DataComponentHolder {
+ }
+
+ public void hurtAndBreak(int amount, ServerLevel world, @Nullable LivingEntity player, Consumer<Item> breakCallback) { // Paper - Add EntityDamageItemEvent
++ int originalDamage = amount; // Paper - Expand PlayerItemDamageEvent
+ int j = this.processDurabilityChange(amount, world, player);
+ // CraftBukkit start
+ if (player instanceof final ServerPlayer serverPlayer) { // Paper - Add EntityDamageItemEvent
+- PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j); // Paper - Add EntityDamageItemEvent
++ PlayerItemDamageEvent event = new PlayerItemDamageEvent(serverPlayer.getBukkitEntity(), CraftItemStack.asCraftMirror(this), j, originalDamage); // Paper - Add EntityDamageItemEvent
+ event.getPlayer().getServer().getPluginManager().callEvent(event);
+
+ if (j != event.getDamage() || event.isCancelled()) {
diff --git a/patches/server/0682-WorldCreator-keepSpawnLoaded.patch b/patches/server/0682-WorldCreator-keepSpawnLoaded.patch
new file mode 100644
index 0000000000..ab38fc7006
--- /dev/null
+++ b/patches/server/0682-WorldCreator-keepSpawnLoaded.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Shane Freeder <[email protected]>
+Date: Sat, 3 Jul 2021 21:18:28 +0100
+Subject: [PATCH] WorldCreator#keepSpawnLoaded
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+index 430b35c35c7aa17d590031515063f2d24eeffe5c..4b894223387bf4029bbfa15d9605247935d7f829 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+@@ -1328,7 +1328,7 @@ public final class CraftServer implements Server {
+ }
+
+ // If set to not keep spawn in memory (changed from default) then adjust rule accordingly
+- if (!creator.keepSpawnInMemory()) {
++ if (creator.keepSpawnLoaded() == net.kyori.adventure.util.TriState.FALSE) { // Paper
+ worlddata.getGameRules().getRule(GameRules.RULE_SPAWN_CHUNK_RADIUS).set(0, null);
+ }
+ ServerLevel internal = (ServerLevel) new ServerLevel(this.console, this.console.executor, worldSession, worlddata, worldKey, worlddimension, this.getServer().progressListenerFactory.create(worlddata.getGameRules().getInt(GameRules.RULE_SPAWN_CHUNK_RADIUS)),
diff --git a/patches/server/0683-Fix-CME-in-CraftPersistentDataTypeRegistry.patch b/patches/server/0683-Fix-CME-in-CraftPersistentDataTypeRegistry.patch
new file mode 100644
index 0000000000..ed8adb4503
--- /dev/null
+++ b/patches/server/0683-Fix-CME-in-CraftPersistentDataTypeRegistry.patch
@@ -0,0 +1,19 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Gero <[email protected]>
+Date: Sat, 2 Oct 2021 20:08:30 +0200
+Subject: [PATCH] Fix CME in CraftPersistentDataTypeRegistry
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataTypeRegistry.java b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataTypeRegistry.java
+index 71dc4a2285ddc86e7aa65ba1a211997ffa8fcce4..20c53aac0aef180ee12de919a8000e4b4bc619ff 100644
+--- a/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataTypeRegistry.java
++++ b/src/main/java/org/bukkit/craftbukkit/persistence/CraftPersistentDataTypeRegistry.java
+@@ -121,7 +121,7 @@ public final class CraftPersistentDataTypeRegistry {
+ }
+ }
+
+- private final Map<Class, TagAdapter> adapters = new HashMap<>();
++ private final Map<Class, TagAdapter> adapters = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - Replace HashMap with ConcurrentHashMap to avoid CME
+
+ /**
+ * Creates a suitable adapter instance for the primitive class type.
diff --git a/patches/server/0684-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch b/patches/server/0684-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch
new file mode 100644
index 0000000000..105d2132f3
--- /dev/null
+++ b/patches/server/0684-Trigger-bee_nest_destroyed-trigger-in-the-correct-pl.patch
@@ -0,0 +1,54 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Wed, 2 Feb 2022 13:50:06 -0800
+Subject: [PATCH] Trigger bee_nest_destroyed trigger in the correct place
+
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+index e000a918230187f6841b03b7b0dd73687f3cc15e..5c3e5c348e6fececccd8097355f423b9e7ad982b 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
+@@ -427,12 +427,16 @@ public class ServerPlayerGameMode {
+ block.destroy(this.level, pos, iblockdata1);
+ }
+
++ ItemStack mainHandStack = null; // Paper - Trigger bee_nest_destroyed trigger in the correct place
++ boolean isCorrectTool = false; // Paper - Trigger bee_nest_destroyed trigger in the correct place
+ if (this.isCreative()) {
+ // return true; // CraftBukkit
+ } else {
+ ItemStack itemstack = this.player.getMainHandItem();
+ ItemStack itemstack1 = itemstack.copy();
+ boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1);
++ mainHandStack = itemstack1; // Paper - Trigger bee_nest_destroyed trigger in the correct place
++ isCorrectTool = flag1; // Paper - Trigger bee_nest_destroyed trigger in the correct place
+
+ itemstack.mineBlock(this.level, iblockdata1, pos, this.player);
+ if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items
+@@ -453,6 +457,13 @@ public class ServerPlayerGameMode {
+ if (flag && event != null) {
+ iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop(), this.player); // Paper
+ }
++ // Paper start - Trigger bee_nest_destroyed trigger in the correct place (check impls of block#playerDestroy)
++ if (mainHandStack != null) {
++ if (flag && isCorrectTool && event.isDropItems() && block instanceof net.minecraft.world.level.block.BeehiveBlock && tileentity instanceof net.minecraft.world.level.block.entity.BeehiveBlockEntity beehiveBlockEntity) { // simulates the guard on block#playerDestroy above
++ CriteriaTriggers.BEE_NEST_DESTROYED.trigger(player, iblockdata, mainHandStack, beehiveBlockEntity.getOccupantCount());
++ }
++ }
++ // Paper end - Trigger bee_nest_destroyed trigger in the correct place
+
+ return true;
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java
+index 4ec03b15bb8a82d92fcb2b7fef70e3c7087a9b68..3e3951cbb196b720256cfc36d6d3b91d48ce3294 100644
+--- a/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/BeehiveBlock.java
+@@ -103,7 +103,7 @@ public class BeehiveBlock extends BaseEntityBlock {
+ this.angerNearbyBees(world, pos);
+ }
+
+- CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount());
++ // CriteriaTriggers.BEE_NEST_DESTROYED.trigger((ServerPlayer) player, state, tool, tileentitybeehive.getOccupantCount()); // Paper - Trigger bee_nest_destroyed trigger in the correct place; moved until after items are dropped
+ }
+
+ }
diff --git a/patches/server/0685-Add-EntityDyeEvent-and-CollarColorable-interface.patch b/patches/server/0685-Add-EntityDyeEvent-and-CollarColorable-interface.patch
new file mode 100644
index 0000000000..79ada84e8b
--- /dev/null
+++ b/patches/server/0685-Add-EntityDyeEvent-and-CollarColorable-interface.patch
@@ -0,0 +1,43 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Fri, 18 Mar 2022 21:15:55 -0700
+Subject: [PATCH] Add EntityDyeEvent and CollarColorable interface
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/animal/Cat.java b/src/main/java/net/minecraft/world/entity/animal/Cat.java
+index 629b282cf27806ff37d67f83d44c06a9f32a9185..9a67dfe214d3eb89d1f4371e716df759651ceb1b 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/Cat.java
++++ b/src/main/java/net/minecraft/world/entity/animal/Cat.java
+@@ -386,6 +386,13 @@ public class Cat extends TamableAnimal implements VariantHolder<Holder<CatVarian
+ DyeColor enumcolor = itemdye.getDyeColor();
+
+ if (enumcolor != this.getCollarColor()) {
++ // Paper start - Add EntityDyeEvent and CollarColorable interface
++ final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity());
++ if (!event.callEvent()) {
++ return InteractionResult.FAIL;
++ }
++ enumcolor = DyeColor.byId(event.getColor().getWoolData());
++ // Paper end - Add EntityDyeEvent and CollarColorable interface
+ if (!this.level().isClientSide()) {
+ this.setCollarColor(enumcolor);
+ itemstack.consume(1, player);
+diff --git a/src/main/java/net/minecraft/world/entity/animal/Wolf.java b/src/main/java/net/minecraft/world/entity/animal/Wolf.java
+index 9581ffee6da72859ea55c3275a163818d4dcdefc..4f23e32dd7b21492a6fcf7b8bd9b4d9d9a9297a3 100644
+--- a/src/main/java/net/minecraft/world/entity/animal/Wolf.java
++++ b/src/main/java/net/minecraft/world/entity/animal/Wolf.java
+@@ -432,6 +432,14 @@ public class Wolf extends TamableAnimal implements NeutralMob, VariantHolder<Hol
+ DyeColor enumcolor = itemdye.getDyeColor();
+
+ if (enumcolor != this.getCollarColor()) {
++ // Paper start - Add EntityDyeEvent and CollarColorable interface
++ final io.papermc.paper.event.entity.EntityDyeEvent event = new io.papermc.paper.event.entity.EntityDyeEvent(this.getBukkitEntity(), org.bukkit.DyeColor.getByWoolData((byte) enumcolor.getId()), ((net.minecraft.server.level.ServerPlayer) player).getBukkitEntity());
++ if (!event.callEvent()) {
++ return InteractionResult.FAIL;
++ }
++ enumcolor = DyeColor.byId(event.getColor().getWoolData());
++ // Paper end - Add EntityDyeEvent and CollarColorable interface
++
+ this.setCollarColor(enumcolor);
+ itemstack.consume(1, player);
+ return InteractionResult.SUCCESS;
diff --git a/patches/server/0686-Fire-CauldronLevelChange-on-initial-fill.patch b/patches/server/0686-Fire-CauldronLevelChange-on-initial-fill.patch
new file mode 100644
index 0000000000..86520e0647
--- /dev/null
+++ b/patches/server/0686-Fire-CauldronLevelChange-on-initial-fill.patch
@@ -0,0 +1,122 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Tue, 29 Mar 2022 13:46:23 -0700
+Subject: [PATCH] Fire CauldronLevelChange on initial fill
+
+Also don't fire level events or game events if stalactite
+drip is cancelled
+
+diff --git a/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java b/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java
+index 637fef0d3cabc867cf861b507b90221e27a711d1..df76185d42075834a39c79515917e03beb938a06 100644
+--- a/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java
++++ b/src/main/java/net/minecraft/core/cauldron/CauldronInteraction.java
+@@ -74,7 +74,7 @@ public interface CauldronInteraction {
+ if (potioncontents != null && potioncontents.is(Potions.WATER)) {
+ if (!world.isClientSide) {
+ // CraftBukkit start
+- if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, Blocks.WATER_CAULDRON.defaultBlockState(), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY)) {
++ if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, Blocks.WATER_CAULDRON.defaultBlockState(), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
+ return InteractionResult.SUCCESS;
+ }
+ // CraftBukkit end
+@@ -129,7 +129,7 @@ public interface CauldronInteraction {
+ if (potioncontents != null && potioncontents.is(Potions.WATER)) {
+ if (!world.isClientSide) {
+ // CraftBukkit start
+- if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata.cycle(LayeredCauldronBlock.LEVEL), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY)) {
++ if (!LayeredCauldronBlock.changeLevel(iblockdata, world, blockposition, iblockdata.cycle(LayeredCauldronBlock.LEVEL), entityhuman, CauldronLevelChangeEvent.ChangeReason.BOTTLE_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
+ return InteractionResult.SUCCESS;
+ }
+ // CraftBukkit end
+@@ -215,7 +215,7 @@ public interface CauldronInteraction {
+ } else {
+ if (!world.isClientSide) {
+ // CraftBukkit start
+- if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL)) {
++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.CAULDRON.defaultBlockState(), player, CauldronLevelChangeEvent.ChangeReason.BUCKET_FILL, false)) { // Paper - Call CauldronLevelChangeEvent
+ return InteractionResult.SUCCESS;
+ }
+ // CraftBukkit end
+@@ -236,7 +236,7 @@ public interface CauldronInteraction {
+ static InteractionResult emptyBucket(Level world, BlockPos pos, Player player, InteractionHand hand, ItemStack stack, BlockState state, SoundEvent soundEvent) {
+ if (!world.isClientSide) {
+ // CraftBukkit start
+- if (!LayeredCauldronBlock.changeLevel(state, world, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY)) {
++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, state, player, CauldronLevelChangeEvent.ChangeReason.BUCKET_EMPTY, false)) { // Paper - Call CauldronLevelChangeEvent
+ return InteractionResult.SUCCESS;
+ }
+ // CraftBukkit end
+diff --git a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java
+index 8d5ad9848d9652b4fd7179c425c47a683ee169ef..c9968934f4ecaa8d81e545f279b3001c7b1ce545 100644
+--- a/src/main/java/net/minecraft/world/level/block/CauldronBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/CauldronBlock.java
+@@ -44,9 +44,19 @@ public class CauldronBlock extends AbstractCauldronBlock {
+ public void handlePrecipitation(BlockState state, Level world, BlockPos pos, Biome.Precipitation precipitation) {
+ if (CauldronBlock.shouldHandlePrecipitation(world, precipitation)) {
+ if (precipitation == Biome.Precipitation.RAIN) {
++ // Paper start - Call CauldronLevelChangeEvent
++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.WATER_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event
++ return;
++ }
++ // Paper end - Call CauldronLevelChangeEvent
+ world.setBlockAndUpdate(pos, Blocks.WATER_CAULDRON.defaultBlockState());
+ world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos);
+ } else if (precipitation == Biome.Precipitation.SNOW) {
++ // Paper start - Call CauldronLevelChangeEvent
++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState(), null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL, false)) { // avoid duplicate game event
++ return;
++ }
++ // Paper end - Call CauldronLevelChangeEvent
+ world.setBlockAndUpdate(pos, Blocks.POWDER_SNOW_CAULDRON.defaultBlockState());
+ world.gameEvent((Entity) null, (Holder) GameEvent.BLOCK_CHANGE, pos);
+ }
+@@ -65,11 +75,19 @@ public class CauldronBlock extends AbstractCauldronBlock {
+
+ if (fluid == Fluids.WATER) {
+ iblockdata1 = Blocks.WATER_CAULDRON.defaultBlockState();
+- LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit
++ // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled
++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit
++ return;
++ }
++ // Paper end - Call CauldronLevelChangeEvent
+ world.levelEvent(1047, pos, 0);
+ } else if (fluid == Fluids.LAVA) {
+ iblockdata1 = Blocks.LAVA_CAULDRON.defaultBlockState();
+- LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL); // CraftBukkit
++ // Paper start - Call CauldronLevelChangeEvent; don't send level event or game event if cancelled
++ if (!LayeredCauldronBlock.changeLevel(state, world, pos, iblockdata1, null, CauldronLevelChangeEvent.ChangeReason.NATURAL_FILL)) { // CraftBukkit
++ return;
++ }
++ // Paper end - Call CauldronLevelChangeEvent
+ world.levelEvent(1046, pos, 0);
+ }
+
+diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java
+index 4ab73a083eba2ad3e12526af0a0dbcfba5cf6c14..806d18689126d0a971665a33d7fc91102ec76db5 100644
+--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java
+@@ -107,7 +107,13 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock {
+ }
+
+ // CraftBukkit start
+- public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, Entity entity, CauldronLevelChangeEvent.ChangeReason reason) {
++ // Paper start - Call CauldronLevelChangeEvent
++ public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason) { // Paper - entity is nullable
++ return changeLevel(iblockdata, world, blockposition, newBlock, entity, reason, true);
++ }
++
++ public static boolean changeLevel(BlockState iblockdata, Level world, BlockPos blockposition, BlockState newBlock, @javax.annotation.Nullable Entity entity, CauldronLevelChangeEvent.ChangeReason reason, boolean sendGameEvent) { // Paper - entity is nullable
++ // Paper end - Call CauldronLevelChangeEvent
+ CraftBlockState newState = CraftBlockStates.getBlockState(world, blockposition);
+ newState.setData(newBlock);
+
+@@ -120,7 +126,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock {
+ return false;
+ }
+ newState.update(true);
+- world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(newBlock));
++ if (sendGameEvent) world.gameEvent((Holder) GameEvent.BLOCK_CHANGE, blockposition, GameEvent.Context.of(newBlock)); // Paper - Call CauldronLevelChangeEvent
+ return true;
+ }
+ // CraftBukkit end
diff --git a/patches/server/0687-fix-powder-snow-cauldrons-not-turning-to-water.patch b/patches/server/0687-fix-powder-snow-cauldrons-not-turning-to-water.patch
new file mode 100644
index 0000000000..201c323a35
--- /dev/null
+++ b/patches/server/0687-fix-powder-snow-cauldrons-not-turning-to-water.patch
@@ -0,0 +1,45 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Thu, 30 Dec 2021 14:02:13 -0800
+Subject: [PATCH] fix powder snow cauldrons not turning to water
+
+Powder snow cauldrons should turn to water when
+extinguishing an entity
+
+diff --git a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java
+index 806d18689126d0a971665a33d7fc91102ec76db5..7dd6b7c0ea472cfbc7ece55bc64bc5d85be4a6c0 100644
+--- a/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java
++++ b/src/main/java/net/minecraft/world/level/block/LayeredCauldronBlock.java
+@@ -73,7 +73,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock {
+ // CraftBukkit start - moved down
+ // entity.clearFire();
+ if (entity.mayInteract(worldserver, pos)) {
+- if (this.handleEntityOnFireInside(state, world, pos, entity)) {
++ if (this.handleEntityOnFireInsideWithEvent(state, world, pos, entity)) { // Paper - fix powdered snow cauldron extinguishing entities
+ entity.clearFire();
+ }
+ // CraftBukkit end
+@@ -84,6 +84,7 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock {
+ }
+
+ // CraftBukkit start
++ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper - fix powdered snow cauldron extinguishing entities; use #handleEntityOnFireInsideWithEvent
+ private boolean handleEntityOnFireInside(BlockState iblockdata, Level world, BlockPos blockposition, Entity entity) {
+ if (this.precipitationType == Biome.Precipitation.SNOW) {
+ return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) iblockdata.getValue(LayeredCauldronBlock.LEVEL)), world, blockposition, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH);
+@@ -93,6 +94,15 @@ public class LayeredCauldronBlock extends AbstractCauldronBlock {
+ }
+
+ }
++ // Paper start - fix powdered snow cauldron extinguishing entities
++ protected boolean handleEntityOnFireInsideWithEvent(BlockState state, Level world, BlockPos pos, Entity entity) {
++ if (this.precipitationType == Biome.Precipitation.SNOW) {
++ return LayeredCauldronBlock.lowerFillLevel((BlockState) Blocks.WATER_CAULDRON.defaultBlockState().setValue(LayeredCauldronBlock.LEVEL, (Integer) state.getValue(LayeredCauldronBlock.LEVEL)), world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH);
++ } else {
++ return LayeredCauldronBlock.lowerFillLevel(state, world, pos, entity, CauldronLevelChangeEvent.ChangeReason.EXTINGUISH);
++ }
++ }
++ // Paper end - fix powdered snow cauldron extinguishing entities
+
+ public static void lowerFillLevel(BlockState state, Level world, BlockPos pos) {
+ // CraftBukkit start
diff --git a/patches/server/0688-Add-PlayerStopUsingItemEvent.patch b/patches/server/0688-Add-PlayerStopUsingItemEvent.patch
new file mode 100644
index 0000000000..8d30122457
--- /dev/null
+++ b/patches/server/0688-Add-PlayerStopUsingItemEvent.patch
@@ -0,0 +1,18 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: u9g <[email protected]>
+Date: Tue, 3 May 2022 20:41:37 -0400
+Subject: [PATCH] Add PlayerStopUsingItemEvent
+
+
+diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+index bb96b962439aa62954352f193b44f97d2e826373..a35ee5cca91512a6b534140fe59df0f8355a097d 100644
+--- a/src/main/java/net/minecraft/world/entity/LivingEntity.java
++++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java
+@@ -4210,6 +4210,7 @@ public abstract class LivingEntity extends Entity implements Attackable {
+
+ public void releaseUsingItem() {
+ if (!this.useItem.isEmpty()) {
++ if (this instanceof ServerPlayer) new io.papermc.paper.event.player.PlayerStopUsingItemEvent((Player) getBukkitEntity(), useItem.asBukkitMirror(), getTicksUsingItem()).callEvent(); // Paper - Add PlayerStopUsingItemEvent
+ this.useItem.releaseUsing(this.level(), this, this.getUseItemRemainingTicks());
+ if (this.useItem.useOnRelease()) {
+ this.updatingUsingItem();
diff --git a/patches/server/0689-Don-t-tick-markers.patch b/patches/server/0689-Don-t-tick-markers.patch
new file mode 100644
index 0000000000..8486b3dbe8
--- /dev/null
+++ b/patches/server/0689-Don-t-tick-markers.patch
@@ -0,0 +1,36 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Noah van der Aa <[email protected]>
+Date: Fri, 7 Jan 2022 11:58:26 +0100
+Subject: [PATCH] Don't tick markers
+
+Fixes https://github.com/PaperMC/Paper/issues/7276 and https://github.com/PaperMC/Paper/issues/8118
+by using a config option that, when set to false, does not add markers to the entity
+tick list at all and ignores them in Spigot's activation range checks. The entity tick
+list is only used in the tick and tickPassenger methods, so we can safely not add the
+markers to it. When the config option is set to true, markers are ticked as normal.
+
+diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java
+index 9d9d133e0d973ecda1ef1efc872a51ee10463fd1..f671b74e4179fc29bc600b52e456ba9f78d8bbd6 100644
+--- a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java
++++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java
+@@ -109,7 +109,7 @@ public final class EntityCommand implements PaperSubcommand {
+ ChunkPos chunk = e.chunkPosition();
+ info.left++;
+ info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1);
+- if (!world.isPositionEntityTicking(e.blockPosition())) {
++ if (!world.isPositionEntityTicking(e.blockPosition()) || (e instanceof net.minecraft.world.entity.Marker && !world.paperConfig().entities.markers.tick)) { // Paper - Configurable marker ticking
+ nonEntityTicking.merge(key, 1, Integer::sum);
+ }
+ });
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index fda4b5f2b848b432138207eff9a77fed6aaf3805..08b819c19eda2ab53b1b37af7a90e38b06b67b85 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -2230,6 +2230,7 @@ public class ServerLevel extends Level implements ServerEntityGetter, WorldGenLe
+ }
+
+ public void onTickingStart(Entity entity) {
++ if (entity instanceof net.minecraft.world.entity.Marker && !paperConfig().entities.markers.tick) return; // Paper - Configurable marker ticking
+ ServerLevel.this.entityTickList.add(entity);
+ }
+
diff --git a/patches/server/0690-Expand-FallingBlock-API.patch b/patches/server/0690-Expand-FallingBlock-API.patch
new file mode 100644
index 0000000000..789dd54c78
--- /dev/null
+++ b/patches/server/0690-Expand-FallingBlock-API.patch
@@ -0,0 +1,107 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Owen1212055 <[email protected]>
+Date: Sun, 5 Dec 2021 14:58:17 -0500
+Subject: [PATCH] Expand FallingBlock API
+
+- add auto expire setting
+- add setter for block data
+- add accessors for block state
+
+== AT ==
+public net.minecraft.world.entity.item.FallingBlockEntity blockState
+
+Co-authored-by: Lukas Planz <[email protected]>
+
+diff --git a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+index eb1745915190e69bb467fca2dbc46e0727530ba0..692d78bff5f7f01c8545e0e1785953ccc0f00e2d 100644
+--- a/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
++++ b/src/main/java/net/minecraft/world/entity/item/FallingBlockEntity.java
+@@ -71,6 +71,7 @@ public class FallingBlockEntity extends Entity {
+ public CompoundTag blockData;
+ public boolean forceTickAfterTeleportToDuplicate;
+ protected static final EntityDataAccessor<BlockPos> DATA_START_POS = SynchedEntityData.defineId(FallingBlockEntity.class, EntityDataSerializers.BLOCK_POS);
++ public boolean autoExpire = true; // Paper - Expand FallingBlock API
+
+ public FallingBlockEntity(EntityType<? extends FallingBlockEntity> type, Level world) {
+ super(type, world);
+@@ -191,7 +192,7 @@ public class FallingBlockEntity extends Entity {
+ }
+
+ if (!this.onGround() && !flag1) {
+- if (this.time > 100 && (blockposition.getY() <= this.level().getMinY() || blockposition.getY() > this.level().getMaxY()) || this.time > 600) {
++ if ((this.time > 100 && autoExpire) && (blockposition.getY() <= this.level().getMinY() || blockposition.getY() > this.level().getMaxY()) || (this.time > 600 && autoExpire)) { // Paper - Expand FallingBlock API
+ if (this.dropItem && worldserver.getGameRules().getBoolean(GameRules.RULE_DOENTITYDROPS)) {
+ this.spawnAtLocation(worldserver, (ItemLike) block);
+ }
+@@ -338,6 +339,7 @@ public class FallingBlockEntity extends Entity {
+ }
+
+ nbt.putBoolean("CancelDrop", this.cancelDrop);
++ if (!autoExpire) {nbt.putBoolean("Paper.AutoExpire", false);} // Paper - Expand FallingBlock API
+ }
+
+ @Override
+@@ -365,6 +367,11 @@ public class FallingBlockEntity extends Entity {
+ this.blockState = Blocks.SAND.defaultBlockState();
+ }
+
++ // Paper start - Expand FallingBlock API
++ if (nbt.contains("Paper.AutoExpire")) {
++ this.autoExpire = nbt.getBoolean("Paper.AutoExpire");
++ }
++ // Paper end - Expand FallingBlock API
+ }
+
+ public void setHurtsEntities(float fallHurtAmount, int fallHurtMax) {
+diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
+index a7a3f74b846112d752fe04162b30805961457b11..1359d25a32b4a5d5e8e68ce737bd19f7b5afaf69 100644
+--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFallingBlock.java
+@@ -33,6 +33,31 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock {
+ public BlockData getBlockData() {
+ return CraftBlockData.fromData(this.getHandle().getBlockState());
+ }
++ // Paper start - Expand FallingBlock API
++ @Override
++ public void setBlockData(final BlockData blockData) {
++ Preconditions.checkArgument(blockData != null, "blockData");
++ final net.minecraft.world.level.block.state.BlockState oldState = this.getHandle().blockState, newState = ((CraftBlockData) blockData).getState();
++ this.getHandle().blockState = newState;
++ this.getHandle().blockData = null;
++
++ if (oldState != newState) this.update();
++ }
++
++ @Override
++ public org.bukkit.block.BlockState getBlockState() {
++ return org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(this.getHandle().blockState, this.getHandle().blockData);
++ }
++
++ @Override
++ public void setBlockState(final org.bukkit.block.BlockState blockState) {
++ Preconditions.checkArgument(blockState != null, "blockState");
++ // Calls #update if needed, the block data compound tag is not synced with the client and hence can be mutated after the sync with clients.
++ // The call also clears any potential old block data.
++ this.setBlockData(blockState.getBlockData());
++ if (blockState instanceof final org.bukkit.craftbukkit.block.CraftBlockEntityState<?> tileEntity) this.getHandle().blockData = tileEntity.getSnapshotNBT();
++ }
++ // Paper end - Expand FallingBlock API
+
+ @Override
+ public boolean getDropItem() {
+@@ -101,4 +126,15 @@ public class CraftFallingBlock extends CraftEntity implements FallingBlock {
+ this.setHurtEntities(true);
+ }
+ }
++ // Paper start - Expand FallingBlock API
++ @Override
++ public boolean doesAutoExpire() {
++ return this.getHandle().autoExpire;
++ }
++
++ @Override
++ public void shouldAutoExpire(boolean autoExpires) {
++ this.getHandle().autoExpire = autoExpires;
++ }
++ // Paper end - Expand FallingBlock API
+ }
diff --git a/patches/server/0691-Add-support-for-Proxy-Protocol.patch b/patches/server/0691-Add-support-for-Proxy-Protocol.patch
new file mode 100644
index 0000000000..7486b23586
--- /dev/null
+++ b/patches/server/0691-Add-support-for-Proxy-Protocol.patch
@@ -0,0 +1,65 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: PanSzelescik <[email protected]>
+Date: Thu, 7 Apr 2022 16:13:39 +0200
+Subject: [PATCH] Add support for Proxy Protocol
+
+
+diff --git a/build.gradle.kts b/build.gradle.kts
+index 3be55cc6e95c02fb43d6e1cd13d05e4e4857ac6b..eaccf005560af84beb98065ea4ac0adaef71768e 100644
+--- a/build.gradle.kts
++++ b/build.gradle.kts
+@@ -41,6 +41,7 @@ dependencies {
+ log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - Needed to generate meta for our Log4j plugins
+ runtimeOnly(log4jPlugins.output)
+ alsoShade(log4jPlugins.output)
++ implementation("io.netty:netty-codec-haproxy:4.1.97.Final") // Paper - Add support for proxy protocol
+ // Paper end
+ implementation("org.apache.logging.log4j:log4j-iostreams:2.24.1") // Paper - remove exclusion
+ implementation("org.ow2.asm:asm-commons:9.7.1")
+diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+index c63c194c44646e6bc1a59426552787011fc2ced5..c62df32af11636ad408b584fcc590590ce4fb0d0 100644
+--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
++++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+@@ -104,6 +104,12 @@ public class ServerConnectionListener {
+ ServerConnectionListener.LOGGER.info("Using default channel type");
+ }
+
++ // Paper start - Warn people with console access that HAProxy is in use.
++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
++ ServerConnectionListener.LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled.");
++ }
++ // Paper end - Warn people with console access that HAProxy is in use.
++
+ this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer<Channel>() {
+ protected void initChannel(Channel channel) {
+ try {
+@@ -123,6 +129,29 @@ public class ServerConnectionListener {
+ Connection object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); // CraftBukkit - decompile error
+
+ //ServerConnectionListener.this.connections.add(object); // Paper
++ // Paper start - Add support for Proxy Protocol
++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) {
++ channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder());
++ channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() {
++ @Override
++ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
++ if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) {
++ if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) {
++ String realaddress = message.sourceAddress();
++ int realport = message.sourcePort();
++
++ SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport);
++
++ Connection connection = (Connection) channel.pipeline().get("packet_handler");
++ connection.address = socketaddr;
++ }
++ } else {
++ super.channelRead(ctx, msg);
++ }
++ }
++ });
++ }
++ // Paper end - Add support for proxy protocol
+ pending.add(object); // Paper - prevent blocking on adding a new connection while the server is ticking
+ ((Connection) object).configurePacketHandler(channelpipeline);
+ ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object));
diff --git a/patches/server/0692-Fix-OfflinePlayer-getBedSpawnLocation.patch b/patches/server/0692-Fix-OfflinePlayer-getBedSpawnLocation.patch
new file mode 100644
index 0000000000..4726c2f65c
--- /dev/null
+++ b/patches/server/0692-Fix-OfflinePlayer-getBedSpawnLocation.patch
@@ -0,0 +1,46 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Mon, 30 May 2022 16:03:36 -0700
+Subject: [PATCH] Fix OfflinePlayer#getBedSpawnLocation
+
+When calling getBedSpawnLocation on an
+instance of CraftOfflinePlayer the world was incorrect
+due to the logic for reading the NBT not being up-to-date.
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
+index e0d342a0ddd140b342f7af138c71596c6d718788..30c1c203022c2ed777dcddfd68ef2e3752c62ea1 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftOfflinePlayer.java
+@@ -37,6 +37,7 @@ import org.bukkit.profile.PlayerProfile;
+
+ @SerializableAs("Player")
+ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializable {
++ private static final org.slf4j.Logger LOGGER = com.mojang.logging.LogUtils.getLogger(); // Paper
+ private final GameProfile profile;
+ private final CraftServer server;
+ private final PlayerDataStorage storage;
+@@ -361,11 +362,20 @@ public class CraftOfflinePlayer implements OfflinePlayer, ConfigurationSerializa
+ if (data == null) return null;
+
+ if (data.contains("SpawnX") && data.contains("SpawnY") && data.contains("SpawnZ")) {
+- String spawnWorld = data.getString("SpawnWorld");
+- if (spawnWorld.equals("")) {
+- spawnWorld = this.server.getWorlds().get(0).getName();
++ // Paper start - fix wrong world
++ final float respawnAngle = data.getFloat("SpawnAngle");
++ org.bukkit.World spawnWorld = this.server.getWorld(data.getString("SpawnWorld")); // legacy
++ if (data.contains("SpawnDimension")) {
++ com.mojang.serialization.DataResult<net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level>> result = net.minecraft.world.level.Level.RESOURCE_KEY_CODEC.parse(net.minecraft.nbt.NbtOps.INSTANCE, data.get("SpawnDimension"));
++ net.minecraft.resources.ResourceKey<net.minecraft.world.level.Level> levelKey = result.resultOrPartial(LOGGER::error).orElse(net.minecraft.world.level.Level.OVERWORLD);
++ net.minecraft.server.level.ServerLevel level = this.server.console.getLevel(levelKey);
++ spawnWorld = level != null ? level.getWorld() : spawnWorld;
+ }
+- return new Location(this.server.getWorld(spawnWorld), data.getInt("SpawnX"), data.getInt("SpawnY"), data.getInt("SpawnZ"));
++ if (spawnWorld == null) {
++ return null;
++ }
++ return new Location(spawnWorld, data.getInt("SpawnX"), data.getInt("SpawnY"), data.getInt("SpawnZ"), respawnAngle, 0);
++ // Paper end
+ }
+ return null;
+ }
diff --git a/patches/server/0693-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch b/patches/server/0693-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch
new file mode 100644
index 0000000000..7158b83635
--- /dev/null
+++ b/patches/server/0693-Fix-FurnaceInventory-for-smokers-and-blast-furnaces.patch
@@ -0,0 +1,49 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Jake Potrebic <[email protected]>
+Date: Sat, 1 Jan 2022 23:11:26 -0800
+Subject: [PATCH] Fix FurnaceInventory for smokers and blast furnaces
+
+
+diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java
+index a6c758c5c5da2fb3f2d251bc109f72a5d8b0eb14..ad2cb9a1352abd855bf11a390c9788835857380a 100644
+--- a/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java
++++ b/src/main/java/org/bukkit/craftbukkit/inventory/util/CraftTileInventoryConverter.java
+@@ -65,7 +65,7 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat
+ return new CraftInventory(tileEntity);
+ }
+
+- public static class Furnace extends CraftTileInventoryConverter {
++ public static class Furnace extends AbstractFurnaceInventoryConverter { // Paper - Furnace, BlastFurnace, and Smoker are pretty much identical
+
+ @Override
+ public Container getTileEntity() {
+@@ -73,6 +73,11 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat
+ return furnace;
+ }
+
++ // Paper start - abstract furnace converter to apply to all 3 furnaces
++ }
++
++ public static abstract class AbstractFurnaceInventoryConverter extends CraftTileInventoryConverter {
++ // Paper end - abstract furnace converter to apply to all 3 furnaces
+ // Paper start
+ @Override
+ public Inventory createInventory(InventoryHolder owner, InventoryType type, net.kyori.adventure.text.Component title) {
+@@ -170,7 +175,7 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat
+ }
+ }
+
+- public static class BlastFurnace extends CraftTileInventoryConverter {
++ public static class BlastFurnace extends AbstractFurnaceInventoryConverter { // Paper - Furnace, BlastFurnace, and Smoker are pretty much identical
+
+ @Override
+ public Container getTileEntity() {
+@@ -186,7 +191,7 @@ public abstract class CraftTileInventoryConverter implements CraftInventoryCreat
+ }
+ }
+
+- public static class Smoker extends CraftTileInventoryConverter {
++ public static class Smoker extends AbstractFurnaceInventoryConverter { // Paper - Furnace, BlastFurnace, and Smoker are pretty much identical
+
+ @Override
+ public Container getTileEntity() {