aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNassim Jahnke <[email protected]>2024-01-24 11:55:36 +0100
committerNassim Jahnke <[email protected]>2024-01-24 11:55:36 +0100
commit51bef80755a18eb3fa58ba42b1926174d5e7a76d (patch)
tree9cb2d82efdc1e5465c0092784ffd455793b779ee
parent11645e32689f9b2872d064e9c8d8199bc34e0354 (diff)
downloadPaper-51bef80755a18eb3fa58ba42b1926174d5e7a76d.tar.gz
Paper-51bef80755a18eb3fa58ba42b1926174d5e7a76d.zip
[ci skip] Remove removed patches
-rw-r--r--patches/removed/0017-Configurable-speed-for-water-flowing-over-lava.patch61
-rw-r--r--patches/removed/0033-Generator-Settings.patch312
-rw-r--r--patches/removed/0035-Stop-updating-flowing-block-if-material-has-changed.patch21
-rw-r--r--patches/removed/0036-Fast-draining.patch113
-rw-r--r--patches/removed/0037-Send-absolute-position-the-first-time-an-entity-is-s.patch87
-rw-r--r--patches/removed/0097-Prevent-Waterflow-BlockFromToEvent-from-loading-chun.patch52
-rw-r--r--patches/removed/0115-Water-mobs-should-only-spawn-in-the-water.patch30
-rw-r--r--patches/removed/0123-SPIGOT-1401-Fix-dispenser-dropper-furnace-placement.patch55
-rw-r--r--patches/removed/0280-Configurable-Unrestricted-Signs.patch83
-rw-r--r--patches/removed/0500-Fix-curing-zombie-villager-discount-exploit.patch31
-rw-r--r--patches/removed/0532-Unload-leaked-Cached-Chunks.patch218
-rw-r--r--patches/removed/1.14/0032-Add-player-view-distance-API.patch280
-rw-r--r--patches/removed/1.14/0061-Chunk-save-queue-improvements.patch206
-rw-r--r--patches/removed/1.14/0070-Optimized-Light-Level-Comparisons.patch90
-rw-r--r--patches/removed/1.14/0071-Pass-world-to-Village-creation.patch36
-rw-r--r--patches/removed/1.14/0078-Optimize-Chunk-Access.patch125
-rw-r--r--patches/removed/1.14/0096-Don-t-spam-reload-spawn-chunks-in-nether-end.patch34
-rw-r--r--patches/removed/1.14/0112-Entity-Tracking-Improvements.patch103
-rw-r--r--patches/removed/1.14/0120-Ensure-Chunks-never-ever-load-async.patch47
-rwxr-xr-xpatches/removed/1.14/0123-Delay-Chunk-Unloads-based-on-Player-Movement.patch224
-rw-r--r--patches/removed/1.14/0142-Prevent-Auto-Save-if-Save-Queue-is-full.patch58
-rw-r--r--patches/removed/1.14/0143-Chunk-Save-Stats-Debug-Option.patch93
-rw-r--r--patches/removed/1.14/0144-Fix-block-break-desync.patch22
-rwxr-xr-xpatches/removed/1.14/0161-ShulkerBox-Dupe-Prevention.patch23
-rw-r--r--patches/removed/1.14/0258-Configurable-Villages-loading-chunks-for-door-checks.patch128
-rw-r--r--patches/removed/1.14/0263-Properly-remove-entities-on-dimension-teleport.patch51
-rw-r--r--patches/removed/1.14/0285-Don-t-process-despawn-if-entity-is-in-a-chunk-schedu.patch29
-rw-r--r--patches/removed/1.14/0341-Optimize-getChunkIfLoaded-type-calls.patch81
-rw-r--r--patches/removed/1.14/0361-Async-Chunk-Loading-and-Generation.patch2503
-rw-r--r--patches/removed/1.14/0363-Optimize-Light-Recalculations.patch52
-rw-r--r--patches/removed/1.14/0366-Fix-Sending-Chunks-to-Client.patch66
-rw-r--r--patches/removed/1.14/0368-Fix-FileIOThread-concurrency-issues.patch35
-rw-r--r--patches/removed/1.14/0377-Optimize-Persistent-Data-Loading.patch66
-rw-r--r--patches/removed/1.14/0388-Use-EntityTypes-for-living-entities.patch872
-rw-r--r--patches/removed/1.14/0400-limit-the-range-at-which-we-ll-consider-an-attackabl.patch35
-rw-r--r--patches/removed/1.15/0078-Reduce-IO-ops-opening-a-new-region-file.patch52
-rw-r--r--patches/removed/1.15/0081-Do-not-load-chunks-for-light-checks.patch23
-rw-r--r--patches/removed/1.15/0269-Provide-option-to-use-a-versioned-world-folder-for-t.patch230
-rw-r--r--patches/removed/1.15/0280-Detect-and-repair-corrupt-Region-Files.patch115
-rw-r--r--patches/removed/1.15/0376-Handle-bad-chunks-more-gracefully.patch63
-rw-r--r--patches/removed/1.15/0403-Preserve-old-flush-on-save-flag-for-reliable-regionf.patch23
-rw-r--r--patches/removed/1.15/0412-Improve-POI-data-saving-logic.patch30
-rw-r--r--patches/removed/1.15/0417-Avoid-Chunk-Lookups-for-Entity-TileEntity-Current-Ch.patch75
-rw-r--r--patches/removed/1.16/0238-Configurable-Bed-Search-Radius.patch159
-rw-r--r--patches/removed/1.16/0298-Support-Overriding-World-Seeds.patch99
-rw-r--r--patches/removed/1.16/0511-Implement-JellySquid-s-Entity-Collision-optimisation.patch101
-rw-r--r--patches/removed/1.16/0512-Remove-some-Streams-usage-in-Entity-Collision.patch178
-rw-r--r--patches/removed/1.16/0530-Optimize-Villagers.patch273
-rw-r--r--patches/removed/1.16/No longer Needed/0276-Send-nearby-packets-from-world-player-list-not-serve.patch82
-rw-r--r--patches/removed/1.16/No longer Needed/0302-Avoid-dimension-id-collisions.patch22
-rw-r--r--patches/removed/1.16/No longer Needed/0316-Fix-MC-93764.patch19
-rw-r--r--patches/removed/1.16/No longer Needed/0373-Fix-some-generation-concurrency-issues.patch223
-rw-r--r--patches/removed/1.16/No longer Needed/0401-Fix-zero-tick-instant-grow-farms-MC-113809.patch96
-rw-r--r--patches/removed/1.16/No longer Needed/0412-Fix-spawn-radius-being-treated-as-0.patch21
-rw-r--r--patches/removed/1.16/No longer Needed/0429-Seed-based-feature-search.patch110
-rw-r--r--patches/removed/1.16/No longer Needed/0468-Port-20w15a-Villager-AI-optimizations-DROP-1.16.patch195
-rw-r--r--patches/removed/1.17/0009-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch171
-rw-r--r--patches/removed/1.17/0010-Store-counts-for-each-Entity-Block-Entity-Type.patch55
-rw-r--r--patches/removed/1.17/0351-Fix-issues-with-entity-loss-due-to-unloaded-chunks.patch53
-rw-r--r--patches/removed/1.17/0372-Reduce-sync-loads.patch33
-rw-r--r--patches/removed/1.17/0380-Optimise-random-block-ticking.patch397
-rw-r--r--patches/removed/1.17/0420-Ensure-Entity-is-never-double-registered.patch81
-rw-r--r--patches/removed/1.17/0436-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch47
-rw-r--r--patches/removed/1.17/0438-Workaround-for-Client-Lag-Spikes-MC-162253.patch101
-rw-r--r--patches/removed/1.17/0626-Optimized-tick-ready-check.patch52
-rw-r--r--patches/removed/1.17/0698-Make-sure-to-remove-correct-TE-during-TE-tick.patch47
-rw-r--r--patches/removed/1.17/No longer needed/0025-Optimize-TileEntity-Ticking.patch281
-rw-r--r--patches/removed/1.17/No longer needed/0040-Send-absolute-position-the-first-time-an-entity-is-s.patch110
-rw-r--r--patches/removed/1.17/No longer needed/0053-Change-implementation-of-tile-entity-removal-list.patch50
-rw-r--r--patches/removed/1.17/No longer needed/0087-Remove-unused-World-Tile-Entity-List.patch93
-rw-r--r--patches/removed/1.17/No longer needed/0088-Don-t-tick-Skulls-unused-code.patch29
-rw-r--r--patches/removed/1.17/No longer needed/0092-Prevent-Fire-from-loading-chunks-wrongly-spread.patch87
-rw-r--r--patches/removed/1.17/No longer needed/0107-Fix-Double-World-Add-issues.patch26
-rw-r--r--patches/removed/1.17/No longer needed/0114-Chunk-registration-fixes.patch24
-rw-r--r--patches/removed/1.17/No longer needed/0120-Cache-user-authenticator-threads.patch107
-rw-r--r--patches/removed/1.17/No longer needed/0135-Add-system-property-to-disable-book-size-limits.patch53
-rw-r--r--patches/removed/1.17/No longer needed/0182-Avoid-NPE-in-PathfinderGoalTempt.patch20
-rw-r--r--patches/removed/1.17/No longer needed/0185-Fix-Dragon-Server-Crashes.patch23
-rw-r--r--patches/removed/1.17/No longer needed/0238-Don-t-change-the-Entity-Random-seed-for-squids.patch20
-rw-r--r--patches/removed/1.17/No longer needed/0239-Re-add-vanilla-entity-warnings-for-duplicates.patch24
-rw-r--r--patches/removed/1.17/No longer needed/0250-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch28
-rw-r--r--patches/removed/1.17/No longer needed/0251-Add-some-Debug-to-Chunk-Entity-slices.patch101
-rw-r--r--patches/removed/1.17/No longer needed/0253-Prevent-Saving-Bad-entities-to-chunks.patch129
-rw-r--r--patches/removed/1.17/No longer needed/0255-Ignore-Dead-Entities-in-entityList-iteration.patch122
-rw-r--r--patches/removed/1.17/No longer needed/0327-Fix-sign-edit-memory-leak.patch46
-rw-r--r--patches/removed/1.17/No longer needed/0327-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch27
-rw-r--r--patches/removed/1.17/No longer needed/0359-Catch-exceptions-from-dispenser-entity-spawns.patch33
-rw-r--r--patches/removed/1.17/No longer needed/0367-Mark-entities-as-being-ticked-when-notifying-navigat.patch26
-rw-r--r--patches/removed/1.17/No longer needed/0380-Performance-improvement-for-Chunk.getEntities.patch39
-rw-r--r--patches/removed/1.17/No longer needed/0421-Fix-unregistering-entities-from-unloading-chunks.patch36
-rw-r--r--patches/removed/1.17/No longer needed/0490-Don-t-mark-null-chunk-sections-for-block-updates.patch19
-rw-r--r--patches/removed/1.17/No longer needed/0501-Fix-enderdragon-exp-dupe.patch32
-rw-r--r--patches/removed/1.17/No longer needed/0506-Limit-lightning-strike-effect-distance.patch77
-rw-r--r--patches/removed/1.17/No longer needed/0541-Import-fastutil-classes.patch28
-rw-r--r--patches/removed/1.17/No longer needed/0543-Remove-armour-stand-double-add-to-world.patch30
-rw-r--r--patches/removed/1.17/No longer needed/0548-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch24
-rw-r--r--patches/removed/1.17/No longer needed/0549-Fix-MC-197271.patch53
-rw-r--r--patches/removed/1.17/No longer needed/0550-MC-197883-Bandaid-decode-issue.patch30
-rw-r--r--patches/removed/1.17/No longer needed/0551-Add-warning-for-servers-not-running-on-Java-16.patch73
-rw-r--r--patches/removed/1.17/No longer needed/0579-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch25
-rw-r--r--patches/removed/1.17/No longer needed/0679-do-not-create-unnecessary-copies-of-passenger-list.patch240
-rw-r--r--patches/removed/1.17/No longer needed/0706-don-t-throw-when-loading-invalid-TEs.patch34
-rw-r--r--patches/removed/1.17/No longer needed/0739-Fix-MC-148809-Increase-structure-block-data-length-t.patch20
-rw-r--r--patches/removed/1.17/state.txt1
-rw-r--r--patches/removed/1.18.2/0025-Remove-invalid-mob-spawner-tile-entities.patch24
-rw-r--r--patches/removed/1.18.2/0085-Workaround-for-setting-passengers-on-players.patch29
-rw-r--r--patches/removed/1.18.2/0108-Remove-FishingHook-reference-on-Craft-Entity-removal.patch27
-rw-r--r--patches/removed/1.18.2/0109-Option-to-remove-corrupt-tile-entities.patch47
-rw-r--r--patches/removed/1.18.2/0115-Don-t-let-fishinghooks-use-portals.patch24
-rw-r--r--patches/removed/1.18.2/0840-Fix-riding-distance-statistics.patch46
-rw-r--r--patches/removed/1.18/0368-No-Tick-view-distance-implementation.patch699
-rw-r--r--patches/removed/1.18/0388-Optimise-TickListServer-by-rewriting-it.patch1127
-rw-r--r--patches/removed/1.18/0436-Optimize-sending-packets-to-nearby-locations-sounds-.patch63
-rw-r--r--patches/removed/1.18/0445-Optimize-WorldBorder-collision-checks-and-air.patch53
-rw-r--r--patches/removed/1.18/0793-Fix-Codec-log-spam.patch197
-rw-r--r--patches/removed/1.18/0836-Cache-palette-array.patch64
-rw-r--r--patches/removed/1.18/No longer needed/0078-Fix-reducedDebugInfo-not-initialized-on-client.patch18
-rw-r--r--patches/removed/1.18/No longer needed/0170-Make-max-squid-spawn-height-configurable.patch37
-rw-r--r--patches/removed/1.18/No longer needed/0228-Optimize-IntIdentityHashBiMiap-nextId.patch68
-rw-r--r--patches/removed/1.18/No longer needed/0312-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch36
-rw-r--r--patches/removed/1.18/No longer needed/0341-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch44
-rw-r--r--patches/removed/1.18/No longer needed/0345-Fix-MC-161754.patch25
-rw-r--r--patches/removed/1.18/No longer needed/0347-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch34
-rw-r--r--patches/removed/1.18/No longer needed/0405-Remove-streams-from-PairedQueue.patch46
-rw-r--r--patches/removed/1.18/No longer needed/0410-Restrict-vanilla-teleport-command-to-valid-locations.patch24
-rw-r--r--patches/removed/1.18/No longer needed/0432-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch102
-rw-r--r--patches/removed/1.18/No longer needed/0463-Optimize-NibbleArray-to-use-pooled-buffers.patch378
-rw-r--r--patches/removed/1.18/No longer needed/0501-Fix-MC-187716-Use-configured-height.patch45
-rw-r--r--patches/removed/1.18/No longer needed/0525-Seed-based-feature-search.patch71
-rw-r--r--patches/removed/1.18/No longer needed/0626-MC-29274-Fix-Wither-hostility-towards-players.patch34
-rw-r--r--patches/removed/1.18/No longer needed/0705-Fix-incorrect-status-dataconverter-for-pre-1.13-chun.patch74
-rw-r--r--patches/removed/1.18/No longer needed/0758-Fix-and-optimize-legacy-world-conversion.patch180
-rw-r--r--patches/removed/1.18/No longer needed/0781-Use-hash-table-for-maintaing-changed-block-set.patch30
-rw-r--r--patches/removed/1.19.1/0906-Untrash-chat-handling.patch100
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0014-ChunkMapDistance-CME.patch84
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0015-Do-not-copy-visible-chunks.patch122
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0016-Chunk-debug-command.patch432
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0017-Make-CallbackExecutor-strict-again.patch48
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0019-Asynchronous-chunk-IO-and-loading.patch3516
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0020-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch1254
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0048-Per-Player-View-Distance-API-placeholders.patch112
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0137-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch36
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0324-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch43
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0355-Fix-Light-Command.patch186
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0393-Optimise-ArraySetSorted-removeIf.patch88
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0397-Fix-Chunk-Post-Processing-deadlock-risk.patch73
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0429-Optimize-ServerLevels-chunk-level-checking-methods.patch69
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0482-Improve-Chunk-Status-Transition-Speed.patch81
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0720-Do-not-allow-the-server-to-unload-chunks-at-request-.patch23
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0722-Correctly-handle-recursion-for-chunkholder-updates.patch37
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0724-Fix-chunks-refusing-to-unload-at-low-TPS.patch26
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0725-Do-not-allow-ticket-level-changes-while-unloading-pl.patch62
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0726-Do-not-allow-ticket-level-changes-when-updating-chun.patch40
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0729-Prevent-unload-calls-removing-tickets-for-sync-loads.patch80
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0734-Rewrite-entity-bounding-box-lookup-calls.patch1300
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0754-Allow-removal-addition-of-entities-to-entity-ticklis.patch89
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0763-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch68
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0801-Actually-unload-POI-data.patch321
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0845-Replace-ticket-level-propagator.patch258
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0853-Replace-player-chunk-loader-system.patch2270
-rw-r--r--patches/removed/1.19.2-legacy-chunksystem/0859-Fix-save-problems-on-shutdown.patch74
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0019-Automatically-disable-plugins-that-fail-to-load.patch21
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0068-Make-plugins-list-alphabetical.patch56
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0104-Close-Plugin-Class-Loaders-on-Disable.patch105
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0125-Add-an-asterisk-to-legacy-API-plugins.patch66
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0134-Remove-deadlock-risk-in-firing-async-events.patch124
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0193-Disable-Sync-Events-firing-Async-errors-during-shutd.patch25
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0193-Make-JavaPluginLoader-thread-safe.patch53
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0204-Prioritise-own-classes-where-possible.patch77
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0206-Provide-a-useful-PluginClassLoader-toString.patch30
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0299-List-all-missing-hard-depends-not-just-first.patch91
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0318-Fix-plugin-provides-load-order.patch27
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0364-Don-t-load-plugins-prefixed-with-a-dot.patch18
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0367-Update-Folder-Uses-Plugin-Name.patch86
-rw-r--r--patches/removed/1.19.3-paper-plugins/api/0413-Future-API-Plans.patch54
-rw-r--r--patches/removed/1.19.3-paper-plugins/server/0223-Add-CraftMagicNumbers.isSupportedApiVersion.patch22
-rw-r--r--patches/removed/1.19.4/0446-Optimize-NetworkManager-Exception-Handling-PARTIAL.patch24
-rw-r--r--patches/removed/1.19.4/0448-Optimize-the-advancement-data-player-iteration-to-be.patch54
-rw-r--r--patches/removed/1.19.4/0739-Add-config-option-for-logging-player-ip-addresses-PARTIAL.patch18
-rw-r--r--patches/removed/1.19/0752-Lag-compensate-block-breaking.patch155
-rw-r--r--patches/removed/1.19/0823-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch65
-rw-r--r--patches/removed/1.19/no-longer-needed/0397-Broadcast-join-message-to-console.patch22
-rw-r--r--patches/removed/1.20.1/0559-Throw-proper-exception-on-empty-JsonList-file.patch18
-rw-r--r--patches/removed/1.20.2/0682-Allow-controlled-flushing-for-network-manager.patch150
-rw-r--r--patches/removed/1.20.2/0696-Consolidate-flush-calls-for-entity-tracker-packets.patch52
-rw-r--r--patches/removed/1.20.2/0704-Optimise-non-flush-packet-sending.patch46
-rw-r--r--patches/removed/1.20.2/0724-Add-config-option-for-logging-player-ip-addresses.patch61
-rw-r--r--patches/removed/1.20.2/0839-Don-t-print-component-in-resource-pack-rejection-mes.patch19
-rw-r--r--patches/removed/1.20.3/0061-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch35
-rw-r--r--patches/removed/1.20.3/0130-Do-not-let-armorstands-drown.patch23
-rw-r--r--patches/removed/1.20.3/0268-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch35
-rw-r--r--patches/removed/1.20.3/0395-Optimize-brigadier-child-sorting-performance.patch28
-rw-r--r--patches/removed/1.20.3/0748-Kick-on-main-for-illegal-chat.patch45
-rw-r--r--patches/removed/1.20.3/0795-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch24
-rw-r--r--patches/removed/1.20.3/0827-Add-some-minimal-debug-information-to-chat-packet-er.patch20
-rw-r--r--patches/removed/1.20.3/0841-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch18
-rw-r--r--patches/removed/1.20.3/1047-Don-t-unpack-loot-table-for-TEs-not-in-world.patch20
-rw-r--r--patches/removed/1.20/0103-Fix-Old-Sign-Conversion.patch49
-rw-r--r--patches/removed/1.20/0342-Increase-Light-Queue-Size.patch28
-rw-r--r--patches/removed/1.20/0407-Reduce-MutableInt-allocations-from-light-engine.patch50
-rw-r--r--patches/removed/1.20/0433-Stop-copy-on-write-operations-for-updating-light-dat.patch297
-rw-r--r--patches/removed/1.20/0571-copy-TESign-isEditable-from-snapshots.patch20
-rw-r--r--patches/removed/1.20/0746-Always-parse-protochunk-light-sources-unless-it-is-m.patch55
-rw-r--r--patches/removed/1.20/0878-Workaround-for-client-lag-spikes-MC-162253.patch116
-rw-r--r--patches/removed/1.20/0880-Fix-EndDragonFight-killed-statuses-should-be-false-f.patch27
-rw-r--r--patches/removed/1.20/0889-Set-position-before-player-sending-on-dimension-chan.patch22
-rw-r--r--patches/removed/1.20/0975-Disable-allowListing-before-received-from-client.patch24
-rw-r--r--scripts/moveback.py2
208 files changed, 1 insertions, 28782 deletions
diff --git a/patches/removed/0017-Configurable-speed-for-water-flowing-over-lava.patch b/patches/removed/0017-Configurable-speed-for-water-flowing-over-lava.patch
deleted file mode 100644
index 12d3df2108..0000000000
--- a/patches/removed/0017-Configurable-speed-for-water-flowing-over-lava.patch
+++ /dev/null
@@ -1,61 +0,0 @@
-From 656824f3d0c0f73417897c1ce3391a91849ec476 Mon Sep 17 00:00:00 2001
-From: Byteflux <[email protected]>
-Date: Tue, 1 Mar 2016 14:27:13 -0600
-Subject: [PATCH] Configurable speed for water flowing over lava
-
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index 4da846719..d3484489b 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -123,4 +123,10 @@ public class PaperWorldConfig {
- if (fallingBlockHeightNerf != 0) log("Falling Block Height Limit set to Y: " + fallingBlockHeightNerf);
- if (entityTNTHeightNerf != 0) log("TNT Entity Height Limit set to Y: " + entityTNTHeightNerf);
- }
-+
-+ public int waterOverLavaFlowSpeed;
-+ private void waterOverLawFlowSpeed() {
-+ waterOverLavaFlowSpeed = getInt("water-over-lava-flow-speed", 5);
-+ log("Water over lava flow speed: " + waterOverLavaFlowSpeed);
-+ }
- }
-diff --git a/src/main/java/net/minecraft/server/BlockFlowing.java b/src/main/java/net/minecraft/server/BlockFlowing.java
-index 7b74df5b9..62234a7c9 100644
---- a/src/main/java/net/minecraft/server/BlockFlowing.java
-+++ b/src/main/java/net/minecraft/server/BlockFlowing.java
-@@ -30,7 +30,7 @@ public class BlockFlowing extends BlockFluids {
- b0 = 2;
- }
-
-- int j = this.a(world);
-+ int j = this.getFlowSpeed(world, blockposition); // Paper
- int k;
-
- if (i > 0) {
-@@ -261,8 +261,22 @@ public class BlockFlowing extends BlockFluids {
-
- public void onPlace(World world, BlockPosition blockposition, IBlockData iblockdata) {
- if (!this.e(world, blockposition, iblockdata)) {
-- world.a(blockposition, (Block) this, this.a(world));
-+ world.a(blockposition, (Block) this, this.getFlowSpeed(world, blockposition)); // Paper
- }
-
- }
-+
-+ /**
-+ * Paper - Get flow speed. Throttle if its water and flowing adjacent to lava
-+ */
-+ public int getFlowSpeed(World world, BlockPosition blockposition) {
-+ if (this.material == Material.WATER && (
-+ world.getType(blockposition.north(1)).getBlock().material == Material.LAVA ||
-+ world.getType(blockposition.south(1)).getBlock().material == Material.LAVA ||
-+ world.getType(blockposition.west(1)).getBlock().material == Material.LAVA ||
-+ world.getType(blockposition.east(1)).getBlock().material == Material.LAVA)) {
-+ return world.paperConfig.waterOverLavaFlowSpeed;
-+ }
-+ return super.a(world);
-+ }
- }
---
-2.18.0
-
diff --git a/patches/removed/0033-Generator-Settings.patch b/patches/removed/0033-Generator-Settings.patch
deleted file mode 100644
index 2d07fcbe61..0000000000
--- a/patches/removed/0033-Generator-Settings.patch
+++ /dev/null
@@ -1,312 +0,0 @@
-From e894331adaa569423be8472929da3a809c4106bb Mon Sep 17 00:00:00 2001
-From: Byteflux <[email protected]>
-Date: Wed, 2 Mar 2016 02:17:54 -0600
-Subject: [PATCH] Generator Settings
-
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index db09711e4..7e5cd8042 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -147,4 +147,34 @@ public class PaperWorldConfig {
- disableEndCredits = getBoolean("game-mechanics.disable-end-credits", false);
- log("End credits disabled: " + disableEndCredits);
- }
-+
-+ public boolean generateCanyon;
-+ public boolean generateCaves;
-+ public boolean generateDungeon;
-+ public boolean generateFortress;
-+ public boolean generateMineshaft;
-+ public boolean generateMonument;
-+ public boolean generateStronghold;
-+ public boolean generateTemple;
-+ public boolean generateVillage;
-+ public boolean generateFlatBedrock;
-+ public boolean disableExtremeHillsEmeralds;
-+ public boolean disableExtremeHillsMonsterEggs;
-+ public boolean disableMesaAdditionalGold;
-+
-+ private void generatorSettings() {
-+ generateCanyon = getBoolean("generator-settings.canyon", true);
-+ generateCaves = getBoolean("generator-settings.caves", true);
-+ generateDungeon = getBoolean("generator-settings.dungeon", true);
-+ generateFortress = getBoolean("generator-settings.fortress", true);
-+ generateMineshaft = getBoolean("generator-settings.mineshaft", true);
-+ generateMonument = getBoolean("generator-settings.monument", true);
-+ generateStronghold = getBoolean("generator-settings.stronghold", true);
-+ generateTemple = getBoolean("generator-settings.temple", true);
-+ generateVillage = getBoolean("generator-settings.village", true);
-+ generateFlatBedrock = getBoolean("generator-settings.flat-bedrock", false);
-+ disableExtremeHillsEmeralds = getBoolean("generator-settings.disable-extreme-hills-emeralds", false);
-+ disableExtremeHillsMonsterEggs = getBoolean("generator-settings.disable-extreme-hills-monster-eggs", false);
-+ disableMesaAdditionalGold = getBoolean("generator-settings.disable-mesa-additional-gold", false);
-+ }
- }
-diff --git a/src/main/java/net/minecraft/server/BiomeBase.java b/src/main/java/net/minecraft/server/BiomeBase.java
-index 1b7599769..ab6db7468 100644
---- a/src/main/java/net/minecraft/server/BiomeBase.java
-+++ b/src/main/java/net/minecraft/server/BiomeBase.java
-@@ -176,7 +176,7 @@ public abstract class BiomeBase {
- BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition();
-
- for (int l1 = 255; l1 >= 0; --l1) {
-- if (l1 <= random.nextInt(5)) {
-+ if (l1 <= (world.paperConfig.generateFlatBedrock ? 0 : random.nextInt(5))) { // Paper - Configurable flat bedrock
- chunksnapshot.a(k1, l1, j1, BiomeBase.c);
- } else {
- IBlockData iblockdata2 = chunksnapshot.a(k1, l1, j1);
-diff --git a/src/main/java/net/minecraft/server/BiomeBigHills.java b/src/main/java/net/minecraft/server/BiomeBigHills.java
-index 9c39bf7af..61680ab50 100644
---- a/src/main/java/net/minecraft/server/BiomeBigHills.java
-+++ b/src/main/java/net/minecraft/server/BiomeBigHills.java
-@@ -32,6 +32,9 @@ public class BiomeBigHills extends BiomeBase {
- int k;
- int l;
-
-+ // Paper start - Disable extreme hills emeralds
-+ if (!world.paperConfig.disableExtremeHillsEmeralds) {
-+
- for (j = 0; j < i; ++j) {
- k = random.nextInt(16);
- l = random.nextInt(28) + 4;
-@@ -43,6 +46,12 @@ public class BiomeBigHills extends BiomeBase {
- }
- }
-
-+ }
-+ // Paper end block
-+
-+ // Paper start - Disable extreme hills monster eggs
-+ if (!world.paperConfig.disableExtremeHillsMonsterEggs) {
-+
- for (i = 0; i < 7; ++i) {
- j = random.nextInt(16);
- k = random.nextInt(64);
-@@ -50,6 +59,9 @@ public class BiomeBigHills extends BiomeBase {
- this.x.generate(world, random, blockposition.a(j, k, l));
- }
-
-+ }
-+ // Paper end block
-+
- }
-
- public void a(World world, Random random, ChunkSnapshot chunksnapshot, int i, int j, double d0) {
-diff --git a/src/main/java/net/minecraft/server/BiomeMesa.java b/src/main/java/net/minecraft/server/BiomeMesa.java
-index f2dd96a32..67f8ad8ed 100644
---- a/src/main/java/net/minecraft/server/BiomeMesa.java
-+++ b/src/main/java/net/minecraft/server/BiomeMesa.java
-@@ -99,7 +99,7 @@ public class BiomeMesa extends BiomeBase {
- chunksnapshot.a(l, i2, k, BiomeMesa.a);
- }
-
-- if (i2 <= random.nextInt(5)) {
-+ if (i2 <= (world.paperConfig.generateFlatBedrock ? 0 : random.nextInt(5))) { // Paper - Configurable flat bedrock
- chunksnapshot.a(l, i2, k, BiomeMesa.c);
- } else if (l1 < 15 || this.I) {
- IBlockData iblockdata2 = chunksnapshot.a(l, i2, k);
-@@ -259,6 +259,7 @@ public class BiomeMesa extends BiomeBase {
-
- protected void a(World world, Random random) {
- super.a(world, random);
-+ if (world.paperConfig.disableMesaAdditionalGold) return; // Paper
- this.a(world, random, 20, this.n, 32, 80);
- }
-
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderFlat.java b/src/main/java/net/minecraft/server/ChunkProviderFlat.java
-index 1452ff657..8b1b79380 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderFlat.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderFlat.java
-@@ -26,7 +26,7 @@ public class ChunkProviderFlat implements ChunkGenerator {
- if (flag) {
- Map map = this.d.b();
-
-- if (map.containsKey("village")) {
-+ if (map.containsKey("village") && world.paperConfig.generateVillage) { // Paper
- Map map1 = (Map) map.get("village");
-
- if (!map1.containsKey("size")) {
-@@ -36,19 +36,19 @@ public class ChunkProviderFlat implements ChunkGenerator {
- this.e.put("Village", new WorldGenVillage(map1));
- }
-
-- if (map.containsKey("biome_1")) {
-+ if (map.containsKey("biome_1") && world.paperConfig.generateTemple) { // Paper
- this.e.put("Temple", new WorldGenLargeFeature((Map) map.get("biome_1")));
- }
-
-- if (map.containsKey("mineshaft")) {
-+ if (map.containsKey("mineshaft") && world.paperConfig.generateMineshaft) { // Paper
- this.e.put("Mineshaft", new WorldGenMineshaft((Map) map.get("mineshaft")));
- }
-
-- if (map.containsKey("stronghold")) {
-+ if (map.containsKey("stronghold") && world.paperConfig.generateStronghold) { // Paper
- this.e.put("Stronghold", new WorldGenStronghold((Map) map.get("stronghold")));
- }
-
-- if (map.containsKey("oceanmonument")) {
-+ if (map.containsKey("oceanmonument") && world.paperConfig.generateMonument) { // Paper
- this.e.put("Monument", new WorldGenMonument((Map) map.get("oceanmonument")));
- }
- }
-@@ -61,7 +61,7 @@ public class ChunkProviderFlat implements ChunkGenerator {
- this.i = new WorldGenLakes(Blocks.LAVA);
- }
-
-- this.g = this.d.b().containsKey("dungeon");
-+ this.g = world.paperConfig.generateDungeon && this.d.b().containsKey("dungeon"); // Paper
- int j = 0;
- int k = 0;
- boolean flag1 = true;
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderGenerate.java b/src/main/java/net/minecraft/server/ChunkProviderGenerate.java
-index 22a24a39f..ee9e00e64 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderGenerate.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderGenerate.java
-@@ -160,32 +160,32 @@ public class ChunkProviderGenerate implements ChunkGenerator {
- this.a(i, j, chunksnapshot);
- this.D = this.n.getWorldChunkManager().getBiomeBlock(this.D, i * 16, j * 16, 16, 16);
- this.a(i, j, chunksnapshot, this.D);
-- if (this.s.r) {
-+ if (this.s.r && this.n.paperConfig.generateCaves) { // Paper
- this.v.a(this.n, i, j, chunksnapshot);
- }
-
-- if (this.s.A) {
-+ if (this.s.A && this.n.paperConfig.generateCanyon) { // Paper
- this.A.a(this.n, i, j, chunksnapshot);
- }
-
- if (this.o) {
-- if (this.s.w) {
-+ if (this.s.w && this.n.paperConfig.generateMineshaft) { // Paper
- this.y.a(this.n, i, j, chunksnapshot);
- }
-
-- if (this.s.v) {
-+ if (this.s.v&& this.n.paperConfig.generateVillage) { // Paper
- this.x.a(this.n, i, j, chunksnapshot);
- }
-
-- if (this.s.u) {
-+ if (this.s.u && this.n.paperConfig.generateStronghold) { // Paper
- this.w.a(this.n, i, j, chunksnapshot);
- }
-
-- if (this.s.x) {
-+ if (this.s.x && this.n.paperConfig.generateTemple) { // Paper
- this.z.a(this.n, i, j, chunksnapshot);
- }
-
-- if (this.s.y) {
-+ if (this.s.y && this.n.paperConfig.generateMonument) { // Paper
- this.B.a(this.n, i, j, chunksnapshot);
- }
-
-@@ -329,23 +329,23 @@ public class ChunkProviderGenerate implements ChunkGenerator {
- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i, j);
-
- if (this.o) {
-- if (this.s.w) {
-+ if (this.s.w && this.n.paperConfig.generateMineshaft) { // Paper
- this.y.a(this.n, this.i, chunkcoordintpair);
- }
-
-- if (this.s.v) {
-+ if (this.s.v && this.n.paperConfig.generateVillage) { // Paper
- flag = this.x.a(this.n, this.i, chunkcoordintpair);
- }
-
-- if (this.s.u) {
-+ if (this.s.u && this.n.paperConfig.generateStronghold) { // Paper
- this.w.a(this.n, this.i, chunkcoordintpair);
- }
-
-- if (this.s.x) {
-+ if (this.s.x && this.n.paperConfig.generateTemple) { // Paper
- this.z.a(this.n, this.i, chunkcoordintpair);
- }
-
-- if (this.s.y) {
-+ if (this.s.y && this.n.paperConfig.generateMonument) { // Paper
- this.B.a(this.n, this.i, chunkcoordintpair);
- }
-
-@@ -374,7 +374,7 @@ public class ChunkProviderGenerate implements ChunkGenerator {
- }
- }
-
-- if (this.s.s) {
-+ if (this.s.s && this.n.paperConfig.generateDungeon) { // Paper
- for (k1 = 0; k1 < this.s.t; ++k1) {
- l1 = this.i.nextInt(16) + 8;
- i2 = this.i.nextInt(256);
-@@ -443,23 +443,23 @@ public class ChunkProviderGenerate implements ChunkGenerator {
-
- public void recreateStructures(Chunk chunk, int i, int j) {
- if (this.o) {
-- if (this.s.w) {
-+ if (this.s.w && this.n.paperConfig.generateMineshaft) { // Paper
- this.y.a(this.n, i, j, (ChunkSnapshot) null);
- }
-
-- if (this.s.v) {
-+ if (this.s.v && this.n.paperConfig.generateVillage) { // Paper
- this.x.a(this.n, i, j, (ChunkSnapshot) null);
- }
-
-- if (this.s.u) {
-+ if (this.s.u && this.n.paperConfig.generateStronghold) { // Paper
- this.w.a(this.n, i, j, (ChunkSnapshot) null);
- }
-
-- if (this.s.x) {
-+ if (this.s.x && this.n.paperConfig.generateTemple) { // Paper
- this.z.a(this.n, i, j, (ChunkSnapshot) null);
- }
-
-- if (this.s.y) {
-+ if (this.s.y && this.n.paperConfig.generateMonument) { // Paper
- this.B.a(this.n, i, j, (ChunkSnapshot) null);
- }
-
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderHell.java b/src/main/java/net/minecraft/server/ChunkProviderHell.java
-index 9f738749f..12bc10ff0 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderHell.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderHell.java
-@@ -151,7 +151,10 @@ public class ChunkProviderHell implements ChunkGenerator {
- IBlockData iblockdata1 = ChunkProviderHell.b;
-
- for (int l1 = 127; l1 >= 0; --l1) {
-- if (l1 < 127 - this.p.nextInt(5) && l1 > this.p.nextInt(5)) {
-+ // Paper start - Configurable flat bedrock worldgen
-+ if (l1 < 127 - (n.paperConfig.generateFlatBedrock ? 0 : this.p.nextInt(5)) &&
-+ l1 > (n.paperConfig.generateFlatBedrock ? 0 : this.p.nextInt(5))) {
-+ // Paper end
- IBlockData iblockdata2 = chunksnapshot.a(i1, l1, l);
-
- if (iblockdata2.getBlock() != null && iblockdata2.getMaterial() != Material.AIR) {
-@@ -384,6 +387,6 @@ public class ChunkProviderHell implements ChunkGenerator {
- }
-
- public void recreateStructures(Chunk chunk, int i, int j) {
-- this.I.a(this.n, i, j, (ChunkSnapshot) null);
-+ if (this.n.paperConfig.generateFortress) this.I.a(this.n, i, j, (ChunkSnapshot) null);
- }
- }
-diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java
-index 66a80a776..34fd7edfe 100644
---- a/src/main/java/net/minecraft/server/StructureGenerator.java
-+++ b/src/main/java/net/minecraft/server/StructureGenerator.java
-@@ -128,6 +128,7 @@ public abstract class StructureGenerator extends WorldGenBase {
- }
-
- public boolean a(World world, BlockPosition blockposition) {
-+ if (this.g == null) return false; // Paper
- this.a(world);
- ObjectIterator objectiterator = this.c.values().iterator();
-
---
-2.18.0
-
diff --git a/patches/removed/0035-Stop-updating-flowing-block-if-material-has-changed.patch b/patches/removed/0035-Stop-updating-flowing-block-if-material-has-changed.patch
deleted file mode 100644
index 4f65bd99ee..0000000000
--- a/patches/removed/0035-Stop-updating-flowing-block-if-material-has-changed.patch
+++ /dev/null
@@ -1,21 +0,0 @@
-From b347181112d7dd6655c11bbd324a2a4c62e9507d Mon Sep 17 00:00:00 2001
-From: Iceee <[email protected]>
-Date: Wed, 2 Mar 2016 12:03:23 -0600
-Subject: [PATCH] Stop updating flowing block if material has changed
-
-
-diff --git a/src/main/java/net/minecraft/server/BlockFlowing.java b/src/main/java/net/minecraft/server/BlockFlowing.java
-index 62234a7c9..3b47253a4 100644
---- a/src/main/java/net/minecraft/server/BlockFlowing.java
-+++ b/src/main/java/net/minecraft/server/BlockFlowing.java
-@@ -90,6 +90,7 @@ public class BlockFlowing extends BlockFluids {
- this.f(world, blockposition, iblockdata);
- }
-
-+ if (world.getType(blockposition).getBlock().getBlockData().getMaterial() != material) return; // Paper - Stop updating flowing block if material has changed
- org.bukkit.block.Block source = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ()); // CraftBukkit
- IBlockData iblockdata2 = world.getType(blockposition.down());
-
---
-2.18.0
-
diff --git a/patches/removed/0036-Fast-draining.patch b/patches/removed/0036-Fast-draining.patch
deleted file mode 100644
index d9f21d81ee..0000000000
--- a/patches/removed/0036-Fast-draining.patch
+++ /dev/null
@@ -1,113 +0,0 @@
-From 4b300fd41621071f122933ad9400521cb46228a0 Mon Sep 17 00:00:00 2001
-From: Byteflux <[email protected]>
-Date: Wed, 2 Mar 2016 12:20:52 -0600
-Subject: [PATCH] Fast draining
-
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index 15675efbf..dbd82d5a9 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -183,4 +183,11 @@ public class PaperWorldConfig {
- optimizeExplosions = getBoolean("optimize-explosions", false);
- log("Optimize explosions: " + optimizeExplosions);
- }
-+
-+ public boolean fastDrainLava;
-+ public boolean fastDrainWater;
-+ private void fastDrain() {
-+ fastDrainLava = getBoolean("fast-drain.lava", false);
-+ fastDrainWater = getBoolean("fast-drain.water", false);
-+ }
- }
-diff --git a/src/main/java/net/minecraft/server/BlockFlowing.java b/src/main/java/net/minecraft/server/BlockFlowing.java
-index 3b47253a4..3aaa19b2f 100644
---- a/src/main/java/net/minecraft/server/BlockFlowing.java
-+++ b/src/main/java/net/minecraft/server/BlockFlowing.java
-@@ -69,7 +69,7 @@ public class BlockFlowing extends BlockFluids {
- }
- }
-
-- if (this.material == Material.LAVA && i < 8 && i1 < 8 && i1 > i && random.nextInt(4) != 0) {
-+ if (!world.paperConfig.fastDrainLava && this.material == Material.LAVA && i < 8 && i1 < 8 && i1 > i && random.nextInt(4) != 0) { // Paper
- j *= 4;
- }
-
-@@ -77,7 +77,7 @@ public class BlockFlowing extends BlockFluids {
- this.f(world, blockposition, iblockdata);
- } else {
- i = i1;
-- if (i1 < 0) {
-+ if (i1 < 0 || canFastDrain(world, blockposition)) { // Paper - Fast draining
- world.setAir(blockposition);
- } else {
- iblockdata = iblockdata.set(BlockFlowing.LEVEL, Integer.valueOf(i1));
-@@ -267,6 +267,7 @@ public class BlockFlowing extends BlockFluids {
-
- }
-
-+ // Paper start
- /**
- * Paper - Get flow speed. Throttle if its water and flowing adjacent to lava
- */
-@@ -280,4 +281,57 @@ public class BlockFlowing extends BlockFluids {
- }
- return super.a(world);
- }
-+
-+ private int getFluidLevel(IBlockAccess iblockaccess, BlockPosition blockposition) {
-+ return iblockaccess.getType(blockposition).getMaterial() == this.material ? iblockaccess.getType(blockposition).get(BlockFluids.LEVEL) : -1;
-+ }
-+
-+ /**
-+ * Paper - Data check method for fast draining
-+ */
-+ public int getData(World world, BlockPosition position) {
-+ int data = this.getFluidLevel((IBlockAccess) world, position);
-+ return data < 8 ? data : 0;
-+ }
-+
-+ /**
-+ * Paper - Checks surrounding blocks to determine if block can be fast drained
-+ */
-+ public boolean canFastDrain(World world, BlockPosition position) {
-+ boolean result = false;
-+ int data = getData(world, position);
-+ if (this.material == Material.WATER) {
-+ if (world.paperConfig.fastDrainWater) {
-+ result = true;
-+ if (getData(world, position.down()) < 0) {
-+ result = false;
-+ } else if (world.getType(position.north()).getBlock().getBlockData().getMaterial() == Material.WATER && getData(world, position.north()) < data) {
-+ result = false;
-+ } else if (world.getType(position.south()).getBlock().getBlockData().getMaterial() == Material.WATER && getData(world, position.south()) < data) {
-+ result = false;
-+ } else if (world.getType(position.west()).getBlock().getBlockData().getMaterial() == Material.WATER && getData(world, position.west()) < data) {
-+ result = false;
-+ } else if (world.getType(position.east()).getBlock().getBlockData().getMaterial() == Material.WATER && getData(world, position.east()) < data) {
-+ result = false;
-+ }
-+ }
-+ } else if (this.material == Material.LAVA) {
-+ if (world.paperConfig.fastDrainLava) {
-+ result = true;
-+ if (getData(world, position.down()) < 0 || world.getType(position.up()).getBlock().getBlockData().getMaterial() != Material.AIR) {
-+ result = false;
-+ } else if (world.getType(position.north()).getBlock().getBlockData().getMaterial() == Material.LAVA && getData(world, position.north()) < data) {
-+ result = false;
-+ } else if (world.getType(position.south()).getBlock().getBlockData().getMaterial() == Material.LAVA && getData(world, position.south()) < data) {
-+ result = false;
-+ } else if (world.getType(position.west()).getBlock().getBlockData().getMaterial() == Material.LAVA && getData(world, position.west()) < data) {
-+ result = false;
-+ } else if (world.getType(position.east()).getBlock().getBlockData().getMaterial() == Material.LAVA && getData(world, position.east()) < data) {
-+ result = false;
-+ }
-+ }
-+ }
-+ return result;
-+ }
-+ // Paper end
- }
---
-2.18.0
-
diff --git a/patches/removed/0037-Send-absolute-position-the-first-time-an-entity-is-s.patch b/patches/removed/0037-Send-absolute-position-the-first-time-an-entity-is-s.patch
deleted file mode 100644
index b04f97045e..0000000000
--- a/patches/removed/0037-Send-absolute-position-the-first-time-an-entity-is-s.patch
+++ /dev/null
@@ -1,87 +0,0 @@
-From fe8bd60fe7b27283c696a5b746414187695174b0 Mon Sep 17 00:00:00 2001
-From: Jedediah Smith <[email protected]>
-Date: Wed, 2 Mar 2016 23:13:07 -0600
-Subject: [PATCH] Send absolute position the first time an entity is seen
-
-
-diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
-index dd6c84b4a2..de0cf6b735 100644
---- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java
-+++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
-@@ -41,7 +41,19 @@ public class EntityTrackerEntry {
- private boolean x;
- private boolean y;
- public boolean b;
-- public final Set<EntityPlayer> trackedPlayers = Sets.newHashSet();
-+ // Paper start
-+ // Replace trackedPlayers Set with a Map. The value is true until the player receives
-+ // their first update (which is forced to have absolute coordinates), false afterward.
-+ public java.util.Map<EntityPlayer, Boolean> trackedPlayerMap = new java.util.HashMap<EntityPlayer, Boolean>();
-+ public Set<EntityPlayer> trackedPlayers = trackedPlayerMap.keySet();
-+
-+ /**
-+ * Requested in https://github.com/PaperMC/Paper/issues/1537 to allow intercepting packets
-+ */
-+ public void sendPlayerPacket(EntityPlayer player, Packet packet) {
-+ player.playerConnection.sendPacket(packet);
-+ }
-+ // Paper end
-
- public EntityTrackerEntry(Entity entity, int i, int j, int k, boolean flag) {
- this.tracker = entity;
-@@ -142,6 +154,7 @@ public class EntityTrackerEntry {
- boolean flag1 = l1 * l1 + i2 * i2 + j2 * j2 >= 128L || this.a % 60 == 0;
- boolean flag2 = Math.abs(j1 - this.yRot) >= 1 || Math.abs(k1 - this.xRot) >= 1;
-
-+ if (this.a > 0 || this.tracker instanceof EntityArrow) { // Paper - Moved up
- // CraftBukkit start - Code moved from below
- if (flag1) {
- this.xLoc = k;
-@@ -155,7 +168,6 @@ public class EntityTrackerEntry {
- }
- // CraftBukkit end
-
-- if (this.a > 0 || this.tracker instanceof EntityArrow) {
- if (l1 >= -32768L && l1 < 32768L && i2 >= -32768L && i2 < 32768L && j2 >= -32768L && j2 < 32768L && this.v <= 400 && !this.x && this.y == this.tracker.onGround) {
- if ((!flag1 || !flag2) && !(this.tracker instanceof EntityArrow)) {
- if (flag1) {
-@@ -201,7 +213,26 @@ public class EntityTrackerEntry {
- }
-
- if (packet1 != null) {
-- this.broadcast((Packet) packet1);
-+ // Paper start - ensure fresh viewers get an absolute position on their first update,
-+ // since we can't be certain what position they received in the spawn packet.
-+ if (packet1 instanceof PacketPlayOutEntityTeleport) {
-+ this.broadcast((Packet) packet1);
-+ } else {
-+ PacketPlayOutEntityTeleport teleportPacket = null;
-+
-+ for (java.util.Map.Entry<EntityPlayer, Boolean> viewer : trackedPlayerMap.entrySet()) {
-+ if (viewer.getValue()) {
-+ viewer.setValue(false);
-+ if (teleportPacket == null) {
-+ teleportPacket = new PacketPlayOutEntityTeleport(this.tracker);
-+ }
-+ sendPlayerPacket(viewer.getKey(), teleportPacket);
-+ } else {
-+ sendPlayerPacket(viewer.getKey(), (Packet) packet1);
-+ }
-+ }
-+ }
-+ // Paper end
- }
-
- this.d();
-@@ -338,7 +369,7 @@ public class EntityTrackerEntry {
-
- entityplayer.removeQueue.remove(Integer.valueOf(this.tracker.getId()));
- // CraftBukkit end
-- this.trackedPlayers.add(entityplayer);
-+ this.trackedPlayerMap.put(entityplayer, true); // Paper
- Packet<?> packet = this.e();
-
- entityplayer.playerConnection.sendPacket(packet);
---
-2.21.0
-
diff --git a/patches/removed/0097-Prevent-Waterflow-BlockFromToEvent-from-loading-chun.patch b/patches/removed/0097-Prevent-Waterflow-BlockFromToEvent-from-loading-chun.patch
deleted file mode 100644
index 432d9ec485..0000000000
--- a/patches/removed/0097-Prevent-Waterflow-BlockFromToEvent-from-loading-chun.patch
+++ /dev/null
@@ -1,52 +0,0 @@
-From 8a9b221bcd755747f0e03bb84f9ba7403eaac7fd Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Mon, 28 Mar 2016 22:03:09 -0400
-Subject: [PATCH] Prevent Waterflow BlockFromToEvent from loading chunks
-
-Many protection plugins would unintentionally trigger chunk loads
-by calling .getToBlock() on an unloaded chunk, killing performance.
-
-Simply skip the event call. as CraftBukkit blocks changing the block
-of unloaded chunks anyways.
-
-This keeps behavior consistent, vs inconsistent flowing based on plugin triggered loads.
-
-diff --git a/src/main/java/net/minecraft/server/BlockFlowing.java b/src/main/java/net/minecraft/server/BlockFlowing.java
-index 739b9aac3..ff90e08eb 100644
---- a/src/main/java/net/minecraft/server/BlockFlowing.java
-+++ b/src/main/java/net/minecraft/server/BlockFlowing.java
-@@ -96,6 +96,7 @@ public class BlockFlowing extends BlockFluids {
-
- if (this.h(world, blockposition.down(), iblockdata2)) {
- // CraftBukkit start
-+ if (!canFlowTo(world, source, BlockFace.DOWN)) { return; } // Paper
- BlockFromToEvent event = new BlockFromToEvent(source, BlockFace.DOWN);
- world.getServer().getPluginManager().callEvent(event);
-
-@@ -136,6 +137,7 @@ public class BlockFlowing extends BlockFluids {
- EnumDirection enumdirection1 = (EnumDirection) iterator1.next();
-
- // CraftBukkit start
-+ if (!canFlowTo(world, source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection1))) { continue; } // Paper
- BlockFromToEvent event = new BlockFromToEvent(source, org.bukkit.craftbukkit.block.CraftBlock.notchToBlockFace(enumdirection1));
- world.getServer().getPluginManager().callEvent(event);
-
-@@ -148,8 +150,14 @@ public class BlockFlowing extends BlockFluids {
-
- }
-
-+ // Paper start
-+ private boolean canFlowTo(World world, org.bukkit.block.Block source, BlockFace face) {
-+ return source.getWorld().isChunkLoaded((source.getX() + face.getModX()) >> 4, (source.getZ() + face.getModZ()) >> 4);
-+ }
-+ // Paper end
-+
- private void flow(World world, BlockPosition blockposition, IBlockData iblockdata, int i) {
-- if (world.isLoaded(blockposition) && this.h(world, blockposition, iblockdata)) { // CraftBukkit - add isLoaded check
-+ if (/*world.isLoaded(blockposition) &&*/ this.h(world, blockposition, iblockdata)) { // CraftBukkit - add isLoaded check // Paper - Already checked before we get here for isLoaded
- if (iblockdata.getMaterial() != Material.AIR) {
- if (this.material == Material.LAVA) {
- this.fizz(world, blockposition);
---
-2.18.0
-
diff --git a/patches/removed/0115-Water-mobs-should-only-spawn-in-the-water.patch b/patches/removed/0115-Water-mobs-should-only-spawn-in-the-water.patch
deleted file mode 100644
index 451ce22a4b..0000000000
--- a/patches/removed/0115-Water-mobs-should-only-spawn-in-the-water.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 3f31cdecde77021afea54370f1fb31ef0621b59f Mon Sep 17 00:00:00 2001
-From: Zach Brown <[email protected]>
-Date: Thu, 14 Apr 2016 17:48:56 -0500
-Subject: [PATCH] Water mobs should only spawn in the water
-
-
-diff --git a/src/main/java/net/minecraft/server/EntityWaterAnimal.java b/src/main/java/net/minecraft/server/EntityWaterAnimal.java
-index f430bdeec..0597edad6 100644
---- a/src/main/java/net/minecraft/server/EntityWaterAnimal.java
-+++ b/src/main/java/net/minecraft/server/EntityWaterAnimal.java
-@@ -11,7 +11,15 @@ public abstract class EntityWaterAnimal extends EntityInsentient implements IAni
- }
-
- public boolean P() {
-- return true;
-+ // Paper start - Don't let water mobs spawn in non-water blocks
-+ // Based around EntityAnimal's implementation
-+ int i = MathHelper.floor(this.locX);
-+ int j = MathHelper.floor(this.getBoundingBox().b); // minY of bounding box
-+ int k = MathHelper.floor(this.locZ);
-+ Block block = this.world.getType(new BlockPosition(i, j, k)).getBlock();
-+
-+ return block == Blocks.WATER || block == Blocks.FLOWING_WATER;
-+ // Paper end
- }
-
- public boolean canSpawn() {
---
-2.18.0
-
diff --git a/patches/removed/0123-SPIGOT-1401-Fix-dispenser-dropper-furnace-placement.patch b/patches/removed/0123-SPIGOT-1401-Fix-dispenser-dropper-furnace-placement.patch
deleted file mode 100644
index 52f6863bab..0000000000
--- a/patches/removed/0123-SPIGOT-1401-Fix-dispenser-dropper-furnace-placement.patch
+++ /dev/null
@@ -1,55 +0,0 @@
-From 7e347eb480cfb1eecd76b0c6fbc08090e439f8d5 Mon Sep 17 00:00:00 2001
-From: Zach Brown <[email protected]>
-Date: Sun, 24 Apr 2016 19:49:33 -0500
-Subject: [PATCH] SPIGOT-1401: Fix dispenser, dropper, furnace placement
-
-
-diff --git a/src/main/java/net/minecraft/server/BlockDispenser.java b/src/main/java/net/minecraft/server/BlockDispenser.java
-index 8e794976a..539b2b3ce 100644
---- a/src/main/java/net/minecraft/server/BlockDispenser.java
-+++ b/src/main/java/net/minecraft/server/BlockDispenser.java
-@@ -20,6 +20,9 @@ public class BlockDispenser extends BlockTileEntity {
- return 4;
- }
-
-+ // Paper start - Removed override of onPlace that was reversing placement direction when
-+ // adjacent to another block, which was not consistent with single player block placement
-+ /*
- public void onPlace(World world, BlockPosition blockposition, IBlockData iblockdata) {
- super.onPlace(world, blockposition, iblockdata);
- this.e(world, blockposition, iblockdata);
-@@ -49,6 +52,8 @@ public class BlockDispenser extends BlockTileEntity {
- world.setTypeAndData(blockposition, iblockdata.set(BlockDispenser.FACING, enumdirection).set(BlockDispenser.TRIGGERED, Boolean.valueOf(false)), 2);
- }
- }
-+ */
-+ // Paper end
-
- public boolean interact(World world, BlockPosition blockposition, IBlockData iblockdata, EntityHuman entityhuman, EnumHand enumhand, EnumDirection enumdirection, float f, float f1, float f2) {
- if (world.isClientSide) {
-diff --git a/src/main/java/net/minecraft/server/BlockFurnace.java b/src/main/java/net/minecraft/server/BlockFurnace.java
-index b6834d2d1..dae711708 100644
---- a/src/main/java/net/minecraft/server/BlockFurnace.java
-+++ b/src/main/java/net/minecraft/server/BlockFurnace.java
-@@ -18,6 +18,9 @@ public class BlockFurnace extends BlockTileEntity {
- return Item.getItemOf(Blocks.FURNACE);
- }
-
-+ // Paper start - Removed override of onPlace that was reversing placement direction when
-+ // adjacent to another block, which was not consistent with single player block placement
-+ /*
- public void onPlace(World world, BlockPosition blockposition, IBlockData iblockdata) {
- this.e(world, blockposition, iblockdata);
- }
-@@ -43,6 +46,8 @@ public class BlockFurnace extends BlockTileEntity {
- world.setTypeAndData(blockposition, iblockdata.set(BlockFurnace.FACING, enumdirection), 2);
- }
- }
-+ */
-+ // Paper end
-
- public boolean interact(World world, BlockPosition blockposition, IBlockData iblockdata, EntityHuman entityhuman, EnumHand enumhand, EnumDirection enumdirection, float f, float f1, float f2) {
- if (world.isClientSide) {
---
-2.18.0
-
diff --git a/patches/removed/0280-Configurable-Unrestricted-Signs.patch b/patches/removed/0280-Configurable-Unrestricted-Signs.patch
deleted file mode 100644
index 3fc4d862d8..0000000000
--- a/patches/removed/0280-Configurable-Unrestricted-Signs.patch
+++ /dev/null
@@ -1,83 +0,0 @@
-From cbe35161f2e8a59c22aca9a0d5412674d128df41 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 21 Mar 2018 19:57:10 -0400
-Subject: [PATCH] Configurable Unrestricted Signs
-
-Bukkit restricts command execution of signs to test if the sender
-has permission to run the specified command. This breaks vanilla
-maps that use signs to intentionally run as elevated permission.
-
-Bukkit provides an unrestricted advancements setting, so this setting
-compliments that one and allows for unrestricted signs.
-
-We still filter sign update packets to strip out commands at edit phase,
-however there is no sanity in ever expecting creative mode to not be
-able to create signs with any command.
-
-Creative servers should absolutely never enable this.
-Non creative servers, enable at own risk!!!
-
-diff --git a/src/main/java/net/minecraft/server/TileEntitySign.java b/src/main/java/net/minecraft/server/TileEntitySign.java
-index 3f2c5b2d5..67bd3bcbe 100644
---- a/src/main/java/net/minecraft/server/TileEntitySign.java
-+++ b/src/main/java/net/minecraft/server/TileEntitySign.java
-@@ -38,7 +38,7 @@ public class TileEntitySign extends TileEntity {
- public void load(NBTTagCompound nbttagcompound) {
- this.isEditable = false;
- super.load(nbttagcompound);
-- ICommandListener icommandlistener = new ICommandListener() {
-+ ICommandListener icommandlistener = new ISignCommandListener() { // Paper
- public String getName() {
- return "Sign";
- }
-@@ -125,7 +125,7 @@ public class TileEntitySign extends TileEntity {
- }
-
- public boolean b(final EntityHuman entityhuman) {
-- ICommandListener icommandlistener = new ICommandListener() {
-+ ICommandListener icommandlistener = new ISignCommandListener() { // Paper
- public String getName() {
- return entityhuman.getName();
- }
-@@ -200,4 +200,5 @@ public class TileEntitySign extends TileEntity {
- public CommandObjectiveExecutor f() {
- return this.i;
- }
-+ public interface ISignCommandListener extends ICommandListener {} // Paper
- }
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index e86c16755..6095948e8 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-@@ -175,6 +175,7 @@ public final class CraftServer implements Server {
- private CraftIconCache icon;
- private boolean overrideAllCommandBlockCommands = false;
- private boolean unrestrictedAdvancements;
-+ private boolean unrestrictedSignCommands; // Paper
- private final List<CraftPlayer> playerView;
- public int reloadCount;
- public static Exception excessiveVelEx; // Paper - Velocity warnings
-@@ -253,6 +254,12 @@ public final class CraftServer implements Server {
- saveCommandsConfig();
- overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*");
- unrestrictedAdvancements = commandsConfiguration.getBoolean("unrestricted-advancements");
-+ // Paper start
-+ unrestrictedSignCommands = commandsConfiguration.getBoolean("unrestricted-signs");
-+ if (unrestrictedSignCommands) {
-+ logger.warning("Warning: Commands are no longer restricted on signs. If you allow players to use Creative Mode, there may be risk of players bypassing permissions. Use this setting at your own risk!!!!");
-+ }
-+ // Paper end
- pluginManager.useTimings(configuration.getBoolean("settings.plugin-profiling"));
- monsterSpawn = configuration.getInt("spawn-limits.monsters");
- animalSpawn = configuration.getInt("spawn-limits.animals");
-@@ -270,6 +277,7 @@ public final class CraftServer implements Server {
- listener = ((CommandListenerWrapper) listener).base;
- }
-
-+ if (unrestrictedSignCommands && listener instanceof TileEntitySign.ISignCommandListener) return true; // Paper
- return unrestrictedAdvancements && listener instanceof AdvancementRewards.AdvancementCommandListener;
- }
-
---
-2.18.0
-
diff --git a/patches/removed/0500-Fix-curing-zombie-villager-discount-exploit.patch b/patches/removed/0500-Fix-curing-zombie-villager-discount-exploit.patch
deleted file mode 100644
index cabbab29ff..0000000000
--- a/patches/removed/0500-Fix-curing-zombie-villager-discount-exploit.patch
+++ /dev/null
@@ -1,31 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: BillyGalbreath <[email protected]>
-Date: Tue, 8 Dec 2020 20:14:20 -0600
-Subject: [PATCH] Fix curing zombie villager discount exploit
-
-This fixes the exploit used to gain absurd trading discounts with infecting
-and curing a villager on repeat by simply resetting the relevant part of
-the reputation when it is cured.
-
-This patch has been removed, as MC-181190 was fixed by mojang in 23w31a.
-
-diff --git a/src/main/java/net/minecraft/world/entity/npc/Villager.java b/src/main/java/net/minecraft/world/entity/npc/Villager.java
-index f58be4e2529759cc64df2c70a69ef56eabbb762d..84cee8fb09f90424438de336f60d9388da1b39de 100644
---- a/src/main/java/net/minecraft/world/entity/npc/Villager.java
-+++ b/src/main/java/net/minecraft/world/entity/npc/Villager.java
-@@ -1009,6 +1009,15 @@ public class Villager extends AbstractVillager implements ReputationEventHandler
- @Override
- public void onReputationEventFrom(ReputationEventType interaction, Entity entity) {
- if (interaction == ReputationEventType.ZOMBIE_VILLAGER_CURED) {
-+ // Paper start - fix MC-181190
-+ if (this.level().paperConfig().fixes.fixCuringZombieVillagerDiscountExploit) {
-+ final GossipContainer.EntityGossips playerReputation = this.getGossips().gossips.get(entity.getUUID());
-+ if (playerReputation != null) {
-+ playerReputation.remove(GossipType.MAJOR_POSITIVE);
-+ playerReputation.remove(GossipType.MINOR_POSITIVE);
-+ }
-+ }
-+ // Paper end
- this.gossips.add(entity.getUUID(), GossipType.MAJOR_POSITIVE, 20);
- this.gossips.add(entity.getUUID(), GossipType.MINOR_POSITIVE, 25);
- } else if (interaction == ReputationEventType.TRADE) {
diff --git a/patches/removed/0532-Unload-leaked-Cached-Chunks.patch b/patches/removed/0532-Unload-leaked-Cached-Chunks.patch
deleted file mode 100644
index 02ac3ee7e4..0000000000
--- a/patches/removed/0532-Unload-leaked-Cached-Chunks.patch
+++ /dev/null
@@ -1,218 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Mon, 25 May 2020 11:02:42 -0400
-Subject: [PATCH] Unload leaked Cached Chunks
-
-Due to some complexity in mojangs complicated chain of juggling
-whether or not a chunk should be unloaded when the last ticket is
-removed, many chunks are remaining around in the cache.
-
-These chunks are never being targetted for unload because they are
-vastly out of view distance range and have no reason to be looked at.
-
-This is a huge issue for performance because we have to iterate these
-chunks EVERY TICK... This is what's been leading to high SELF time in
-Ticking Chunks timings/profiler results.
-
-We will now detect these chunks in that iteration, and automatically
-add it to the unload queue when the chunk is found without any tickets.
-
-diff --git a/src/main/java/net/minecraft/server/ChunkMapDistance.java b/src/main/java/net/minecraft/server/ChunkMapDistance.java
-index 9805361e2d49fa1cfecf0c5811187fc503d0ad8e..62ed8e653c2060c314b407f698842e9c7c3312c0 100644
---- a/src/main/java/net/minecraft/server/ChunkMapDistance.java
-+++ b/src/main/java/net/minecraft/server/ChunkMapDistance.java
-@@ -33,7 +33,7 @@ public abstract class ChunkMapDistance {
- public final Long2ObjectOpenHashMap<ArraySetSorted<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
- private final ChunkMapDistance.a e = new ChunkMapDistance.a();
- public static final int MOB_SPAWN_RANGE = 8; //private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
-- private final ChunkMapDistance.c g = new ChunkMapDistance.c(33);
-+ private final ChunkMapDistance.c g = new ChunkMapDistance.c(33); public final ChunkMapDistance.c getLevelTracker() { return g; } // Paper
- // Paper start use a queue, but still keep unique requirement
- public final java.util.Queue<PlayerChunk> pendingChunkUpdates = new java.util.ArrayDeque<PlayerChunk>() {
- @Override
-@@ -478,7 +478,7 @@ public abstract class ChunkMapDistance {
- if (flag1) {
- ChunkMapDistance.this.j.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error
- ChunkMapDistance.this.m.execute(() -> {
-- if (this.c(this.c(i))) {
-+ if (this.c(this.c(i))) { // Paper - diff above isChunkLoaded
- ChunkMapDistance.this.addTicket(i, ticket);
- ChunkMapDistance.this.l.add(i);
- } else {
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-index 54e89c9cc6c47ff2c4f4dd5d4c22a391f8a3d6e0..144e91b303cbd9c58c9e6d598e9c9334f2a75c73 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-@@ -560,6 +560,7 @@ public class ChunkProviderServer extends IChunkProvider {
- }
- }
- // Paper start
-+ if (playerchunk != null) playerchunk.lastActivity = world.getTime(); // Paper
- CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> future = this.a(playerchunk, l) ? PlayerChunk.UNLOADED_CHUNK_ACCESS_FUTURE : playerchunk.a(chunkstatus, this.playerChunkMap);
- if (isUrgent) {
- future.thenAccept(either -> this.chunkMapDistance.clearUrgent(chunkcoordintpair));
-@@ -812,6 +813,7 @@ public class ChunkProviderServer extends IChunkProvider {
- this.world.timings.countNaturalMobs.stopTiming(); // Paper - timings
- this.world.getMethodProfiler().exit();
- // Paper - replaced by above
-+ final long time = world.getTime(); // Paper
- final int[] chunksTicked = {0}; this.playerChunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping
- Optional<Chunk> optional = ((Either) playerchunk.b().getNow(PlayerChunk.UNLOADED_CHUNK)).left();
-
-@@ -897,7 +899,7 @@ public class ChunkProviderServer extends IChunkProvider {
- this.world.timings.chunkTicks.stopTiming(); // Spigot // Paper
- if (chunksTicked[0]++ % 10 == 0) this.world.getMinecraftServer().midTickLoadChunks(); // Paper
- }
-- }
-+ } else { checkInactiveChunk(playerchunk, time); } // Paper - check inaccessible chunks
- });
- this.world.getMethodProfiler().enter("customSpawners");
- if (flag1) {
-@@ -913,6 +915,30 @@ public class ChunkProviderServer extends IChunkProvider {
- this.playerChunkMap.g();
- }
-
-+ // Paper start - remove inaccessible chunks leaked
-+ private void checkInactiveChunk(PlayerChunk playerchunk, long time) {
-+ int ticketLevel = playerchunk.getTicketLevel();
-+ if (ticketLevel > 33 && ticketLevel == playerchunk.oldTicketLevel &&
-+ (playerchunk.lastActivity == 0 || time - playerchunk.lastActivity > 20*120) &&
-+ playerchunk.location.pair() % 20 == 0 && playerChunkMap.unloadQueue.size() < 100
-+ ) {
-+ ChunkStatus chunkHolderStatus = playerchunk.getChunkHolderStatus();
-+ ChunkStatus desiredStatus = PlayerChunk.getChunkStatus(ticketLevel);
-+ if (chunkHolderStatus != null && !chunkHolderStatus.isAtLeastStatus(desiredStatus)) {
-+ return;
-+ }
-+ if (playerchunk.lastActivity == 0) {
-+ playerchunk.lastActivity = time;
-+ return;
-+ }
-+ playerchunk.lastActivity = time;
-+ if (playerchunk.shouldBeUnloaded()) {
-+ playerChunkMap.unloadQueue.add(playerchunk.location.pair());
-+ }
-+ }
-+ }
-+ // Paper end
-+
- @Override
- public String getName() {
- return "ServerChunkCache: " + this.h();
-diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
-index b8fe42e8123e972b1ec97b048c35d90118076e66..18f9a2590f7fa5dfc9070fc5e13121d77f06e79f 100644
---- a/src/main/java/net/minecraft/server/PlayerChunk.java
-+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
-@@ -44,6 +44,22 @@ public class PlayerChunk {
-
- long lastAutoSaveTime; // Paper - incremental autosave
- long inactiveTimeStart; // Paper - incremental autosave
-+ // Paper start - unload leaked chunks
-+ long lastActivity;
-+ java.util.concurrent.ConcurrentHashMap<ChunkCoordIntPair, Boolean> dependendedOnBy = new java.util.concurrent.ConcurrentHashMap<>();
-+ public boolean shouldBeUnloaded() {
-+ if (!neighborPriorities.isEmpty() || !dependendedOnBy.isEmpty()) {
-+ return false;
-+ }
-+ long key = location.pair();
-+ if (chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(key) != null) {
-+ return false;
-+ }
-+ PlayerChunkMap.a distanceManager = chunkMap.chunkDistanceManager;
-+ ArraySetSorted<Ticket<?>> tickets = distanceManager.tickets.get(key);
-+ return (tickets == null || tickets.isEmpty()) TODO: Figure out level map;
-+ }
-+ // Paper end
-
- // Paper start - optimise isOutsideOfRange
- // cached here to avoid a map lookup
-@@ -562,6 +578,7 @@ public class PlayerChunk {
- protected void a(PlayerChunkMap playerchunkmap) {
- ChunkStatus chunkstatus = getChunkStatus(this.oldTicketLevel);
- ChunkStatus chunkstatus1 = getChunkStatus(this.ticketLevel);
-+ if (oldTicketLevel != ticketLevel) lastActivity = chunkMap.world.getTime(); // Paper - chunk leak
- boolean flag = this.oldTicketLevel <= PlayerChunkMap.GOLDEN_TICKET;
- boolean flag1 = this.ticketLevel <= PlayerChunkMap.GOLDEN_TICKET; // Paper - diff on change: (flag1 = new ticket level is in loadable range)
- PlayerChunk.State playerchunk_state = getChunkState(this.oldTicketLevel);
-diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
-index f42507f5a17f9388db738218f58ca76f863274ff..9ee13c741fdcc6b40d175e375e61bffa4c14f45f 100644
---- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
-+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
-@@ -639,6 +639,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
- }
- }));
- }
-+ playerchunk.lastActivity = world.getTime(); // Paper - chunk leak
-
- ChunkStatus chunkstatus = (ChunkStatus) intfunction.apply(j1);
- CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture = playerchunk.a(chunkstatus, this);
-@@ -646,6 +647,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
- if (requestingNeighbor != null && requestingNeighbor != playerchunk && !completablefuture.isDone()) {
- requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus);
- completablefuture.thenAccept(either -> {
-+ playerchunk.lastActivity = world.getTime(); // Paper - chunk leak
- requestingNeighbor.onNeighborDone(playerchunk, chunkstatus, either.left().orElse(null));
- });
- }
-@@ -874,6 +876,12 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
- PlayerChunk playerchunk = (PlayerChunk) this.updatingChunks.remove(j);
-
- if (playerchunk != null) {
-+ // Paper start - don't unload chunks that should be loaded
-+ if (!playerchunk.shouldBeUnloaded()) {
-+ this.updatingChunks.put(j, playerchunk);
-+ continue;
-+ }
-+ // Paper end
- this.pendingUnload.put(j, playerchunk);
- this.updatingChunksModified = true;
- this.a(j, playerchunk); // Paper - Move up - don't leak chunks
-@@ -1147,9 +1155,27 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
- return completablefuture.thenComposeAsync((either) -> {
- return either.map((list) -> { // Paper - Shut up.
- try {
-+ // Paper start
-+ list.forEach(chunk -> {
-+ PlayerChunk updatingChunk = getUpdatingChunk(chunk.getPos().pair());
-+ if (updatingChunk != null) {
-+ updatingChunk.dependendedOnBy.put(playerchunk.location, true);
-+ updatingChunk.lastActivity = world.getTime();
-+ }
-+ });
-+ // Paper end
- CompletableFuture<Either<IChunkAccess, PlayerChunk.Failure>> completablefuture1 = chunkstatus.a(this.world, this.chunkGenerator, this.definedStructureManager, this.lightEngine, (ichunkaccess) -> {
- return this.c(playerchunk);
- }, list);
-+ // Paper start
-+ completablefuture1.whenComplete((unused, unused2) -> list.forEach(chunk -> {
-+ PlayerChunk updatingChunk = getUpdatingChunk(chunk.getPos().pair());
-+ if (updatingChunk != null) {
-+ updatingChunk.dependendedOnBy.remove(playerchunk.location);
-+ updatingChunk.lastActivity = world.getTime();
-+ }
-+ }));
-+ // Paper end
-
- this.worldLoadListener.a(chunkcoordintpair, chunkstatus);
- return completablefuture1;
-@@ -1167,6 +1193,7 @@ public class PlayerChunkMap extends IChunkLoader implements PlayerChunk.d {
- return CompletableFuture.completedFuture(Either.right(playerchunk_failure));
- });
- }, (runnable) -> {
-+ playerchunk.lastActivity = world.getTime(); // Paper
- this.mailboxWorldGen.a(ChunkTaskQueueSorter.a(playerchunk, runnable)); // CraftBukkit - decompile error
- });
- }
-diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java
-index acfe732af5b9f63fc2f6b78499defabe2e73ee45..25b19346fc1c702cc37275d0ec16abbbfacfb418 100644
---- a/src/main/java/net/minecraft/server/StructureGenerator.java
-+++ b/src/main/java/net/minecraft/server/StructureGenerator.java
-@@ -160,7 +160,7 @@ public abstract class StructureGenerator<C extends WorldGenFeatureConfiguration>
- while (longiterator.hasNext()) {
- long k = longiterator.nextLong();
- IChunkAccess ichunkaccess1 = generatoraccess.getChunkAt(ChunkCoordIntPair.getX(k), ChunkCoordIntPair.getZ(k), ChunkStatus.STRUCTURE_STARTS, false); // CraftBukkit - don't load chunks
-- StructureStart structurestart = ichunkaccess1.a(this.b());
-+ StructureStart structurestart = ichunkaccess1 != null ? ichunkaccess1.a(this.b()) : null; // Paper - make sure not null
-
- if (structurestart != null) {
- list.add(structurestart);
diff --git a/patches/removed/1.14/0032-Add-player-view-distance-API.patch b/patches/removed/1.14/0032-Add-player-view-distance-API.patch
deleted file mode 100644
index 237194dbab..0000000000
--- a/patches/removed/1.14/0032-Add-player-view-distance-API.patch
+++ /dev/null
@@ -1,280 +0,0 @@
-From 22baa6be4daa8a11770411b50f1fbf545196407d Mon Sep 17 00:00:00 2001
-From: Byteflux <[email protected]>
-Date: Wed, 2 Mar 2016 14:35:27 -0600
-Subject: [PATCH] Add player view distance API
-
-
-diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java
-index 58e037e13b..e97bb2305c 100644
---- a/src/main/java/net/minecraft/server/EntityHuman.java
-+++ b/src/main/java/net/minecraft/server/EntityHuman.java
-@@ -71,6 +71,15 @@ public abstract class EntityHuman extends EntityLiving {
- // Paper start
- public boolean affectsSpawning = true;
- // Paper end
-+ // Paper start - Player view distance API
-+ private int viewDistance = -1;
-+ public int getViewDistance() {
-+ return viewDistance == -1 ? ((WorldServer) world).getPlayerChunkMap().getViewDistance() : viewDistance;
-+ }
-+ public void setViewDistance(int viewDistance) {
-+ this.viewDistance = viewDistance;
-+ }
-+ // Paper end
-
- // CraftBukkit start
- public boolean fauxSleeping;
-diff --git a/src/main/java/net/minecraft/server/EntityTracker.java b/src/main/java/net/minecraft/server/EntityTracker.java
-index 45ab33d1ae..3854ae9769 100644
---- a/src/main/java/net/minecraft/server/EntityTracker.java
-+++ b/src/main/java/net/minecraft/server/EntityTracker.java
-@@ -200,6 +200,7 @@ public class EntityTracker {
-
- }
-
-+ public void updatePlayer(EntityPlayer entityplayer) { a(entityplayer); } // Paper - OBFHELPER
- public void a(EntityPlayer entityplayer) {
- Iterator iterator = this.c.iterator();
-
-diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
-index d00401ce14..dd6c84b4a2 100644
---- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java
-+++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
-@@ -435,7 +435,7 @@ public class EntityTrackerEntry {
- public boolean c(EntityPlayer entityplayer) {
- double d0 = entityplayer.locX - (double) this.xLoc / 4096.0D;
- double d1 = entityplayer.locZ - (double) this.zLoc / 4096.0D;
-- int i = Math.min(this.e, this.f);
-+ int i = Math.min(this.e, (entityplayer.getViewDistance() - 1) * 16); // Paper - Use player view distance API
-
- return d0 >= (double) (-i) && d0 <= (double) i && d1 >= (double) (-i) && d1 <= (double) i && this.tracker.a(entityplayer);
- }
-diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
-index e01222ad2b..55161af9c9 100644
---- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
-+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
-@@ -33,7 +33,7 @@ public class PlayerChunkMap {
- private final List<PlayerChunk> g = Lists.newLinkedList();
- private final List<PlayerChunk> h = Lists.newLinkedList();
- private final List<PlayerChunk> i = Lists.newArrayList();
-- private int j;
-+ private int j;public int getViewDistance() { return j; } // Paper OBFHELPER
- private long k;
- private boolean l = true;
- private boolean m = true;
-@@ -257,8 +257,11 @@ public class PlayerChunkMap {
- // CraftBukkit start - Load nearby chunks first
- List<ChunkCoordIntPair> chunkList = new LinkedList<ChunkCoordIntPair>();
-
-- for (int k = i - this.j; k <= i + this.j; ++k) {
-- for (int l = j - this.j; l <= j + this.j; ++l) {
-+ // Paper start - Player view distance API
-+ int viewDistance = entityplayer.getViewDistance();
-+ for (int k = i - viewDistance; k <= i + viewDistance; ++k) {
-+ for (int l = j - viewDistance; l <= j + viewDistance; ++l) {
-+ // Paper end
- chunkList.add(new ChunkCoordIntPair(k, l));
- }
- }
-@@ -277,8 +280,11 @@ public class PlayerChunkMap {
- int i = (int) entityplayer.d >> 4;
- int j = (int) entityplayer.e >> 4;
-
-- for (int k = i - this.j; k <= i + this.j; ++k) {
-- for (int l = j - this.j; l <= j + this.j; ++l) {
-+ // Paper start - Player view distance API
-+ int viewDistance = entityplayer.getViewDistance();
-+ for (int k = i - viewDistance; k <= i + viewDistance; ++k) {
-+ for (int l = j - viewDistance; l <= j + viewDistance; ++l) {
-+ // Paper end
- PlayerChunk playerchunk = this.getChunk(k, l);
-
- if (playerchunk != null) {
-@@ -308,7 +314,8 @@ public class PlayerChunkMap {
- if (d2 >= 64.0D) {
- int k = (int) entityplayer.d >> 4;
- int l = (int) entityplayer.e >> 4;
-- int i1 = this.j;
-+ int i1 = entityplayer.getViewDistance(); // Paper - Player view distance API
-+
- int j1 = i - k;
- int k1 = j - l;
-
-@@ -352,6 +359,8 @@ public class PlayerChunkMap {
- return playerchunk != null && playerchunk.d(entityplayer) && playerchunk.e();
- }
-
-+ public final void setViewDistanceForAll(int viewDistance) { this.a(viewDistance); } // Paper - OBFHELPER
-+ // Paper start - Separate into two methods
- public void a(int i) {
- i = MathHelper.clamp(i, 3, 32);
- if (i != this.j) {
-@@ -361,36 +370,55 @@ public class PlayerChunkMap {
-
- while (iterator.hasNext()) {
- EntityPlayer entityplayer = (EntityPlayer) iterator.next();
-- int k = (int) entityplayer.locX >> 4;
-- int l = (int) entityplayer.locZ >> 4;
-- int i1;
-- int j1;
--
-- if (j > 0) {
-- for (i1 = k - i; i1 <= k + i; ++i1) {
-- for (j1 = l - i; j1 <= l + i; ++j1) {
-- PlayerChunk playerchunk = this.c(i1, j1);
--
-- if (!playerchunk.d(entityplayer)) {
-- playerchunk.a(entityplayer);
-- }
-+ this.setViewDistance(entityplayer, i, false); // Paper - Split, don't mark sort pending, we'll handle it after
-+ }
-+
-+ this.j = i;
-+ this.e();
-+ }
-+ }
-+
-+ public void setViewDistance(EntityPlayer entityplayer, int i) {
-+ this.setViewDistance(entityplayer, i, true); // Mark sort pending by default so we don't have to remember to do so all the time
-+ }
-+
-+ // Copied from above with minor changes
-+ public void setViewDistance(EntityPlayer entityplayer, int i, boolean markSort) {
-+ i = MathHelper.clamp(i, 3, 32);
-+ int oldViewDistance = entityplayer.getViewDistance();
-+ if (i != oldViewDistance) {
-+ int j = i - oldViewDistance;
-+
-+ int k = (int) entityplayer.locX >> 4;
-+ int l = (int) entityplayer.locZ >> 4;
-+ int i1;
-+ int j1;
-+
-+ if (j > 0) {
-+ for (i1 = k - i; i1 <= k + i; ++i1) {
-+ for (j1 = l - i; j1 <= l + i; ++j1) {
-+ PlayerChunk playerchunk = this.c(i1, j1);
-+
-+ if (!playerchunk.d(entityplayer)) {
-+ playerchunk.a(entityplayer);
- }
- }
-- } else {
-- for (i1 = k - this.j; i1 <= k + this.j; ++i1) {
-- for (j1 = l - this.j; j1 <= l + this.j; ++j1) {
-- if (!this.a(i1, j1, k, l, i)) {
-- this.c(i1, j1).b(entityplayer);
-- }
-+ }
-+ } else {
-+ for (i1 = k - oldViewDistance; i1 <= k + oldViewDistance; ++i1) {
-+ for (j1 = l - oldViewDistance; j1 <= l + oldViewDistance; ++j1) {
-+ if (!this.a(i1, j1, k, l, i)) {
-+ this.c(i1, j1).b(entityplayer);
- }
- }
- }
-+ if (markSort) {
-+ this.e();
-+ }
- }
--
-- this.j = i;
-- this.e();
- }
- }
-+ // Paper end
-
- private void e() {
- this.l = true;
-@@ -469,4 +497,32 @@ public class PlayerChunkMap {
- }
- }
- // CraftBukkit end
-+
-+ // Paper start - Player view distance API
-+ public void updateViewDistance(EntityPlayer player, int distanceIn) {
-+ final int oldViewDistance = player.getViewDistance();
-+
-+ // This represents the view distance that we will set on the player
-+ // It can exist as a negative value
-+ int playerViewDistance = MathHelper.clamp(distanceIn, 3, 32);
-+
-+ // This value is the one we actually use to update the chunk map
-+ // We don't ever want this to be a negative
-+ int toSet = playerViewDistance;
-+
-+ if (distanceIn < 0) {
-+ playerViewDistance = -1;
-+ toSet = world.getPlayerChunkMap().getViewDistance();
-+ }
-+
-+ if (toSet != oldViewDistance) {
-+ // Order matters
-+ this.setViewDistance(player, toSet);
-+ player.setViewDistance(playerViewDistance);
-+
-+ //Force update entity trackers
-+ this.getWorld().getTracker().updatePlayer(player);
-+ }
-+ }
-+ // Paper end
- }
-diff --git a/src/main/java/net/minecraft/server/SpawnerCreature.java b/src/main/java/net/minecraft/server/SpawnerCreature.java
-index 4eaa5d93b4..6720a9648e 100644
---- a/src/main/java/net/minecraft/server/SpawnerCreature.java
-+++ b/src/main/java/net/minecraft/server/SpawnerCreature.java
-@@ -44,7 +44,7 @@ public final class SpawnerCreature {
- boolean flag3 = true;
- // Spigot Start
- byte b0 = worldserver.spigotConfig.mobSpawnRange;
-- b0 = ( b0 > worldserver.spigotConfig.viewDistance ) ? (byte) worldserver.spigotConfig.viewDistance : b0;
-+ b0 = ( b0 > entityhuman.getViewDistance() ) ? (byte) entityhuman.getViewDistance() : b0; // Paper - Use player view distance API
- b0 = ( b0 > 8 ) ? 8 : b0;
-
- for (int i1 = -b0; i1 <= b0; ++i1) {
-diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-index 0ee063bcd3..5496fae409 100644
---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-@@ -1626,6 +1626,16 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
- public boolean getAffectsSpawning() {
- return this.getHandle().affectsSpawning;
- }
-+
-+ @Override
-+ public int getViewDistance() {
-+ return getHandle().getViewDistance();
-+ }
-+
-+ @Override
-+ public void setViewDistance(int viewDistance) {
-+ ((WorldServer) getHandle().world).getPlayerChunkMap().updateViewDistance(getHandle(), viewDistance);
-+ }
- // Paper end
-
- @Override
-diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
-index a95f93eb76..09df00e94b 100644
---- a/src/main/java/org/spigotmc/ActivationRange.java
-+++ b/src/main/java/org/spigotmc/ActivationRange.java
-@@ -108,13 +108,13 @@ public class ActivationRange
-
- int maxRange = Math.max( monsterActivationRange, animalActivationRange );
- maxRange = Math.max( maxRange, miscActivationRange );
-- maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange );
-+ //maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange ); Paper - Use player view distance API below instead
-
- for ( EntityHuman player : world.players )
- {
--
-+ int playerMaxRange = maxRange = Math.min( ( player.getViewDistance() << 4 ) - 8, maxRange ); // Paper - Use player view distance API
- player.activatedTick = MinecraftServer.currentTick;
-- maxBB = player.getBoundingBox().grow( maxRange, 256, maxRange );
-+ maxBB = player.getBoundingBox().grow( playerMaxRange, 256, playerMaxRange ); // Paper - Use player view distance API
- miscBB = player.getBoundingBox().grow( miscActivationRange, 256, miscActivationRange );
- animalBB = player.getBoundingBox().grow( animalActivationRange, 256, animalActivationRange );
- monsterBB = player.getBoundingBox().grow( monsterActivationRange, 256, monsterActivationRange );
---
-2.21.0
-
diff --git a/patches/removed/1.14/0061-Chunk-save-queue-improvements.patch b/patches/removed/1.14/0061-Chunk-save-queue-improvements.patch
deleted file mode 100644
index ff4cd129a7..0000000000
--- a/patches/removed/1.14/0061-Chunk-save-queue-improvements.patch
+++ /dev/null
@@ -1,206 +0,0 @@
-From 208077ba7ae7f41d3bdfcc4f5bc577faa8095905 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Fri, 4 Mar 2016 18:18:37 -0600
-Subject: [PATCH] Chunk save queue improvements
-
-For some unknown reason, Minecraft is sleeping 10ms between every single chunk being saved to disk.
-Under high chunk load/unload activity (lots of movement / teleporting), this causes the chunk unload queue
-to build up in size.
-
-This has multiple impacts:
-1) Performance of the unload queue itself - The save thread is pretty ineffecient for how it accesses it
- By letting the queue get larger, checking and popping work off the queue can get less performant.
-2) Performance of chunk loading - As with #1, chunk loads also have to check this queue when loading
- chunk data so that it doesn't load stale data if new data is pending write to disk.
-3) Memory Usage - The entire chunk has been serialized to NBT, and now sits in this queue. This leads to
- elevated memory usage, and then the objects used in the serialization sit around longer than needed,
- resulting in promotion to Old Generation instead of dying young.
-
-To optimize this, we change the entire unload queue to be a proper queue. This improves the behavior of popping
-the first queued chunk off, instead of abusing iterators like Mojang was doing.
-
-This also improves reliability of chunk saving, as the previous hack job had a race condition that could
-fail to save some chunks.
-
-Then finally, Sleeping will by default be removed, but due to known issues with 1.9, a config option was added.
-But if sleeps are to remain enabled, we at least lower the sleep interval so it doesn't have as much negative impact.
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-index cfcc244672..4e932ea235 100644
---- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-@@ -217,4 +217,10 @@ public class PaperConfig {
- " - Interval: " + timeSummary(Timings.getHistoryInterval() / 20) +
- " - Length: " + timeSummary(Timings.getHistoryLength() / 20));
- }
-+
-+ public static boolean enableFileIOThreadSleep;
-+ private static void enableFileIOThreadSleep() {
-+ enableFileIOThreadSleep = getBoolean("settings.sleep-between-chunk-saves", false);
-+ if (enableFileIOThreadSleep) Bukkit.getLogger().info("Enabled sleeping between chunk saves, beware of memory issues");
-+ }
- }
-diff --git a/src/main/java/net/minecraft/server/ChunkCoordIntPair.java b/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
-index b0c004b1f2..d2cece2651 100644
---- a/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
-+++ b/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
-@@ -20,6 +20,7 @@ public class ChunkCoordIntPair {
- this.z = (int) (i >> 32);
- }
-
-+ public long asLong() { return a(); } // Paper
- public long a() {
- return a(this.x, this.z);
- }
-diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-index 35976a26f3..21ee154a57 100644
---- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-@@ -20,6 +20,7 @@ import java.util.Map.Entry;
- import java.util.function.Consumer;
- import java.util.function.Function;
- import javax.annotation.Nullable;
-+import java.util.concurrent.ConcurrentLinkedQueue; // Paper
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
- // Spigot start
-@@ -29,8 +30,28 @@ import org.spigotmc.SupplierUtils;
-
- public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
-
-+ // Paper start - Chunk queue improvements
-+ private static class QueuedChunk {
-+ public ChunkCoordIntPair coords;
-+ public Supplier<NBTTagCompound> compoundSupplier;
-+ public Runnable onSave;
-+
-+ public QueuedChunk(Runnable run) {
-+ this.coords = null;
-+ this.compoundSupplier = null;
-+ this.onSave = run;
-+ }
-+
-+ public QueuedChunk(ChunkCoordIntPair coords, Supplier<NBTTagCompound> compoundSupplier) {
-+ this.coords = coords;
-+ this.compoundSupplier = compoundSupplier;
-+ }
-+ }
-+ final private ConcurrentLinkedQueue<QueuedChunk> queue = new ConcurrentLinkedQueue<>();
-+ // Paper end
-+
- private static final Logger a = LogManager.getLogger();
-- private final Map<ChunkCoordIntPair, Supplier<NBTTagCompound>> b = Maps.newHashMap();
-+ private final it.unimi.dsi.fastutil.longs.Long2ObjectMap<Supplier<NBTTagCompound>> saveMap = it.unimi.dsi.fastutil.longs.Long2ObjectMaps.synchronize(new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>()); // Paper
- private final File c;
- private final DataFixer d;
- private PersistentStructureLegacy e;
-@@ -86,7 +107,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- return null;
- }
- // CraftBukkit end
-- NBTTagCompound nbttagcompound = SupplierUtils.getIfExists(this.b.get(new ChunkCoordIntPair(i, j))); // Spigot
-+ NBTTagCompound nbttagcompound = SupplierUtils.getIfExists(this.saveMap.get(ChunkCoordIntPair.asLong(i, j))); // Spigot // Paper
-
- if (nbttagcompound != null) {
- return nbttagcompound;
-@@ -314,7 +335,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- };
- }
-
-- this.a(chunkcoordintpair, SupplierUtils.createUnivaluedSupplier(completion, unloaded && this.b.size() < SAVE_QUEUE_TARGET_SIZE));
-+ this.a(chunkcoordintpair, SupplierUtils.createUnivaluedSupplier(completion, unloaded)); // Paper - Remove save queue target size
- // Spigot end
- } catch (Exception exception) {
- ChunkRegionLoader.a.error("Failed to save chunk", exception);
-@@ -323,7 +344,8 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- }
-
- protected void a(ChunkCoordIntPair chunkcoordintpair, Supplier<NBTTagCompound> nbttagcompound) { // Spigot
-- this.b.put(chunkcoordintpair, nbttagcompound);
-+ this.saveMap.put(chunkcoordintpair.asLong(), nbttagcompound); // Paper
-+ queue.add(new QueuedChunk(chunkcoordintpair, nbttagcompound)); // Paper - Chunk queue improvements
- FileIOThread.a().a(this);
- }
-
-@@ -333,20 +355,24 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- }
-
- private boolean processSaveQueueEntry(boolean logCompletion) {
-- Iterator<Entry<ChunkCoordIntPair, Supplier<NBTTagCompound>>> iterator = this.b.entrySet().iterator(); // Spigot
--
-- if (!iterator.hasNext()) {
-+ // Paper start - Chunk queue improvements
-+ QueuedChunk chunk = queue.poll();
-+ if (chunk == null) {
-+ // Paper - end
- if (logCompletion) { // CraftBukkit
- ChunkRegionLoader.a.info("ThreadedAnvilChunkStorage ({}): All chunks are saved", this.c.getName());
- }
-
- return false;
- } else {
-- Entry<ChunkCoordIntPair, NBTTagCompound> entry = (Entry) iterator.next();
--
-- iterator.remove();
-- ChunkCoordIntPair chunkcoordintpair = (ChunkCoordIntPair) entry.getKey();
-- Supplier<NBTTagCompound> nbttagcompound = (Supplier<NBTTagCompound>) entry.getValue(); // Spigot
-+ // Paper start
-+ if (chunk.onSave != null) {
-+ chunk.onSave.run();
-+ return true;
-+ }
-+ // Paper end
-+ ChunkCoordIntPair chunkcoordintpair = chunk.coords; // Paper - Chunk queue improvements
-+ Supplier<NBTTagCompound> nbttagcompound = chunk.compoundSupplier; // Spigot // Paper
-
- if (nbttagcompound == null) {
- return true;
-@@ -355,6 +381,15 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- // CraftBukkit start
- RegionFileCache.write(this.c, chunkcoordintpair.x, chunkcoordintpair.z, SupplierUtils.getIfExists(nbttagcompound)); // Spigot
-
-+ // Paper start remove from map only if this was the latest version of the chunk
-+ synchronized (this.saveMap) {
-+ long k = chunkcoordintpair.asLong();
-+ // This will not equal if a newer version is still pending - wait until newest is saved to remove
-+ if (this.saveMap.get(k) == chunk.compoundSupplier) {
-+ this.saveMap.remove(k);
-+ }
-+ }
-+ // Paper end
- /*
- NBTCompressedStreamTools.a(nbttagcompound, (DataOutput) dataoutputstream);
- dataoutputstream.close();
-diff --git a/src/main/java/net/minecraft/server/FileIOThread.java b/src/main/java/net/minecraft/server/FileIOThread.java
-index 8c3537ab8d..3c688f546c 100644
---- a/src/main/java/net/minecraft/server/FileIOThread.java
-+++ b/src/main/java/net/minecraft/server/FileIOThread.java
-@@ -38,20 +38,21 @@ public class FileIOThread implements Runnable {
- IAsyncChunkSaver iasyncchunksaver = (IAsyncChunkSaver) this.c.get(i);
- boolean flag;
-
-- synchronized (iasyncchunksaver) {
-+ //synchronized (iasyncchunksaver) { // Paper - remove synchronized
- flag = iasyncchunksaver.a();
-- }
-+ //} // Paper
-
- if (!flag) {
- this.c.remove(i--);
- ++this.e;
- }
-
-+ if (com.destroystokyo.paper.PaperConfig.enableFileIOThreadSleep) { // Paper
- try {
-- Thread.sleep(this.f ? 0L : 10L);
-+ Thread.sleep(this.f ? 0L : 1L); // Paper
- } catch (InterruptedException interruptedexception) {
- interruptedexception.printStackTrace();
-- }
-+ }} // Paper
- }
-
- if (this.c.isEmpty()) {
---
-2.21.0
-
diff --git a/patches/removed/1.14/0070-Optimized-Light-Level-Comparisons.patch b/patches/removed/1.14/0070-Optimized-Light-Level-Comparisons.patch
deleted file mode 100644
index 17ae006bf7..0000000000
--- a/patches/removed/1.14/0070-Optimized-Light-Level-Comparisons.patch
+++ /dev/null
@@ -1,90 +0,0 @@
-From 62de9801d97be7f583f88f20b374660bb4349cc8 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Fri, 18 Mar 2016 21:22:56 -0400
-Subject: [PATCH] Optimized Light Level Comparisons
-
-Use an optimized method to test if a block position meets a desired light level.
-
-This method benefits from returning as soon as the desired light level matches.
-
-diff --git a/src/main/java/net/minecraft/server/BlockCrops.java b/src/main/java/net/minecraft/server/BlockCrops.java
-index fe0dde1461..9d53f1118c 100644
---- a/src/main/java/net/minecraft/server/BlockCrops.java
-+++ b/src/main/java/net/minecraft/server/BlockCrops.java
-@@ -44,7 +44,7 @@ public class BlockCrops extends BlockPlant implements IBlockFragilePlantElement
-
- public void a(IBlockData iblockdata, World world, BlockPosition blockposition, Random random) {
- super.a(iblockdata, world, blockposition, random);
-- if (world.getLightLevel(blockposition.up(), 0) >= 9) {
-+ if (world.isLightLevel(blockposition.up(), 9)) { // Paper
- int i = this.k(iblockdata);
-
- if (i < this.e()) {
-diff --git a/src/main/java/net/minecraft/server/BlockSapling.java b/src/main/java/net/minecraft/server/BlockSapling.java
-index 81ea9bcb4f..291cc9a398 100644
---- a/src/main/java/net/minecraft/server/BlockSapling.java
-+++ b/src/main/java/net/minecraft/server/BlockSapling.java
-@@ -30,7 +30,7 @@ public class BlockSapling extends BlockPlant implements IBlockFragilePlantElemen
-
- public void a(IBlockData iblockdata, World world, BlockPosition blockposition, Random random) {
- super.a(iblockdata, world, blockposition, random);
-- if (world.getLightLevel(blockposition.up()) >= 9 && random.nextInt(Math.max(2, (int) (((100.0F / world.spigotConfig.saplingModifier) * 7) + 0.5F))) == 0) { // Spigot
-+ if (world.isLightLevel(blockposition.up(), 9) && random.nextInt(Math.max(2, (int) (((100.0F / world.spigotConfig.saplingModifier) * 7) + 0.5F))) == 0) { // Spigot // Paper
- // CraftBukkit start
- world.captureTreeGeneration = true;
- // CraftBukkit end
-diff --git a/src/main/java/net/minecraft/server/BlockStem.java b/src/main/java/net/minecraft/server/BlockStem.java
-index 53f091835c..f8dda1b7a1 100644
---- a/src/main/java/net/minecraft/server/BlockStem.java
-+++ b/src/main/java/net/minecraft/server/BlockStem.java
-@@ -27,7 +27,7 @@ public class BlockStem extends BlockPlant implements IBlockFragilePlantElement {
-
- public void a(IBlockData iblockdata, World world, BlockPosition blockposition, Random random) {
- super.a(iblockdata, world, blockposition, random);
-- if (world.getLightLevel(blockposition.up(), 0) >= 9) {
-+ if (world.isLightLevel(blockposition.up(), 9)) { // Paper
- float f = BlockCrops.a((Block) this, (IBlockAccess) world, blockposition);
-
- if (random.nextInt((int) ((100.0F / (this == Blocks.PUMPKIN_STEM ? world.spigotConfig.pumpkinModifier : world.spigotConfig.melonModifier)) * (25.0F / f)) + 1) == 0) { // Spigot
-diff --git a/src/main/java/net/minecraft/server/EntityMonster.java b/src/main/java/net/minecraft/server/EntityMonster.java
-index 5ea5170436..dc61263a3f 100644
---- a/src/main/java/net/minecraft/server/EntityMonster.java
-+++ b/src/main/java/net/minecraft/server/EntityMonster.java
-@@ -66,9 +66,18 @@ public abstract class EntityMonster extends EntityCreature implements IMonster {
- if (this.world.getBrightness(EnumSkyBlock.SKY, blockposition) > this.random.nextInt(32)) {
- return false;
- } else {
-- int i = this.world.Y() ? this.world.d(blockposition, 10) : this.world.getLightLevel(blockposition);
--
-- return i <= this.random.nextInt(8);
-+ // Paper start - optimized light check, returns faster
-+ boolean passes;
-+ if (this.world.Y()) {
-+ final int orig = world.getSkylightSubtracted();
-+ world.setSkylightSubtracted(10);
-+ passes = !this.world.isLightLevel(blockposition, this.random.nextInt(8));
-+ world.setSkylightSubtracted(orig);
-+ } else {
-+ passes = !this.world.isLightLevel(blockposition, this.random.nextInt(8));
-+ }
-+ return passes;
-+ // Paper end
- }
- }
-
-diff --git a/src/main/java/net/minecraft/server/EntityZombie.java b/src/main/java/net/minecraft/server/EntityZombie.java
-index 002be7f7be..7a943a6c27 100644
---- a/src/main/java/net/minecraft/server/EntityZombie.java
-+++ b/src/main/java/net/minecraft/server/EntityZombie.java
-@@ -267,7 +267,7 @@ public class EntityZombie extends EntityMonster {
- int j1 = j + MathHelper.nextInt(this.random, 7, 40) * MathHelper.nextInt(this.random, -1, 1);
- int k1 = k + MathHelper.nextInt(this.random, 7, 40) * MathHelper.nextInt(this.random, -1, 1);
-
-- if (this.world.getType(new BlockPosition(i1, j1 - 1, k1)).q() && this.world.getLightLevel(new BlockPosition(i1, j1, k1)) < 10) {
-+ if (this.world.getType(new BlockPosition(i1, j1 - 1, k1)).q() && !this.world.isLightLevel(new BlockPosition(i1, j1, k1), 10)) { // Paper
- entityzombie.setPosition((double) i1, (double) j1, (double) k1);
- if (!this.world.isPlayerNearby((double) i1, (double) j1, (double) k1, 7.0D) && this.world.a_(entityzombie, entityzombie.getBoundingBox()) && this.world.getCubes(entityzombie, entityzombie.getBoundingBox()) && !this.world.containsLiquid(entityzombie.getBoundingBox())) {
- this.world.addEntity(entityzombie, CreatureSpawnEvent.SpawnReason.REINFORCEMENTS); // CraftBukkit
---
-2.21.0
-
diff --git a/patches/removed/1.14/0071-Pass-world-to-Village-creation.patch b/patches/removed/1.14/0071-Pass-world-to-Village-creation.patch
deleted file mode 100644
index d601e351ab..0000000000
--- a/patches/removed/1.14/0071-Pass-world-to-Village-creation.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-From b61f0153ccd138435700cfb4c711f477112574f6 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 19 Mar 2016 15:16:54 -0400
-Subject: [PATCH] Pass world to Village creation
-
-fixes NPE bug #95
-
-diff --git a/src/main/java/net/minecraft/server/PersistentVillage.java b/src/main/java/net/minecraft/server/PersistentVillage.java
-index 98c6bbc183..7a9fb97530 100644
---- a/src/main/java/net/minecraft/server/PersistentVillage.java
-+++ b/src/main/java/net/minecraft/server/PersistentVillage.java
-@@ -237,7 +237,7 @@ public class PersistentVillage extends PersistentBase {
-
- for (int i = 0; i < nbttaglist.size(); ++i) {
- NBTTagCompound nbttagcompound1 = nbttaglist.getCompound(i);
-- Village village = new Village();
-+ Village village = new Village(world); // Paper
-
- village.a(nbttagcompound1);
- this.villages.add(village);
-diff --git a/src/main/java/net/minecraft/server/Village.java b/src/main/java/net/minecraft/server/Village.java
-index 68ad7bc213..b794572915 100644
---- a/src/main/java/net/minecraft/server/Village.java
-+++ b/src/main/java/net/minecraft/server/Village.java
-@@ -24,7 +24,7 @@ public class Village {
- private final List<Village.Aggressor> k;
- private int l;
-
-- public Village() {
-+ private Village() { // Paper - Nothing should call this - world needs to be set.
- this.c = BlockPosition.ZERO;
- this.d = BlockPosition.ZERO;
- this.j = Maps.newHashMap();
---
-2.21.0
-
diff --git a/patches/removed/1.14/0078-Optimize-Chunk-Access.patch b/patches/removed/1.14/0078-Optimize-Chunk-Access.patch
deleted file mode 100644
index 0fac66c023..0000000000
--- a/patches/removed/1.14/0078-Optimize-Chunk-Access.patch
+++ /dev/null
@@ -1,125 +0,0 @@
-From 11ab12d6efda726da59ca55d164a9ba49af71e59 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Thu, 27 Aug 2015 01:15:02 -0400
-Subject: [PATCH] Optimize Chunk Access
-
-getting a loaded chunk is one of the most hottest pieces of code in the game.
-getChunkAt is called for the same chunk multiple times in a row, often from getType();
-
-Optimize this look up by using a Last Access cache.
-
-diff --git a/src/main/java/net/minecraft/server/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java
-index 732c8793e5..8b3738c8f7 100644
---- a/src/main/java/net/minecraft/server/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/ChunkMap.java
-@@ -15,6 +15,7 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
-
- public Chunk put(long i, Chunk chunk) {
- chunk.world.timings.syncChunkLoadPostTimer.startTiming(); // Paper
-+ lastChunkByPos = chunk; // Paper
- Chunk chunk1 = (Chunk) super.put(i, chunk);
- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i);
-
-@@ -22,7 +23,7 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- for (int k = chunkcoordintpair.z - 1; k <= chunkcoordintpair.z + 1; ++k) {
- if (j != chunkcoordintpair.x || k != chunkcoordintpair.z) {
- long l = ChunkCoordIntPair.a(j, k);
-- Chunk chunk2 = (Chunk) this.get(l);
-+ Chunk chunk2 = (Chunk) super.get(l); // Paper - use super to avoid polluting last access cache
-
- if (chunk2 != null) {
- chunk.H();
-@@ -40,7 +41,7 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- continue;
- }
-
-- Chunk neighbor = this.get(ChunkCoordIntPair.a(chunkcoordintpair.x + x, chunkcoordintpair.z + z));
-+ Chunk neighbor = super.get(ChunkCoordIntPair.a(chunkcoordintpair.x + x, chunkcoordintpair.z + z)); // Paper - use super to avoid polluting last access cache
- if (neighbor != null) {
- neighbor.setNeighborLoaded(-x, -z);
- chunk.setNeighborLoaded(x, z);
-@@ -64,7 +65,7 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- for (int j = chunkcoordintpair.x - 1; j <= chunkcoordintpair.x + 1; ++j) {
- for (int k = chunkcoordintpair.z - 1; k <= chunkcoordintpair.z + 1; ++k) {
- if (j != chunkcoordintpair.x || k != chunkcoordintpair.z) {
-- Chunk chunk1 = (Chunk) this.get(ChunkCoordIntPair.a(j, k));
-+ Chunk chunk1 = (Chunk) super.get(ChunkCoordIntPair.a(j, k)); // Paper - use super to avoid polluting last access cache
-
- if (chunk1 != null) {
- chunk1.I();
-@@ -73,8 +74,22 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- }
- }
-
-+ // Paper start
-+ if (lastChunkByPos != null && i == lastChunkByPos.chunkKey) {
-+ lastChunkByPos = null;
-+ }
- return chunk;
- }
-+ private Chunk lastChunkByPos = null;
-+
-+ @Override
-+ public Chunk get(long l) {
-+ if (lastChunkByPos != null && l == lastChunkByPos.chunkKey) {
-+ return lastChunkByPos;
-+ }
-+ return lastChunkByPos = super.get(l);
-+ }
-+ // Paper end
-
- public Chunk remove(Object object) {
- return this.remove((Long) object);
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-index 1ed7c7e2c9..c54df45837 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-@@ -76,15 +76,16 @@ public class ChunkProviderServer implements IChunkProvider {
- Chunk chunk;
-
- synchronized (this.chunkLoader) {
-- if (this.lastChunk != null && this.lastChunk.getPos().x == i && this.lastChunk.getPos().z == j) {
-+ // Paper start - remove vanilla lastChunk, we do it more accurately
-+ /* if (this.lastChunk != null && this.lastChunk.locX == i && this.lastChunk.locZ == j) {
- return this.lastChunk;
-- }
-+ }*/ // Paper end
-
- long k = ChunkCoordIntPair.a(i, j);
-
- chunk = (Chunk) this.chunks.get(k);
- if (chunk != null) {
-- this.lastChunk = chunk;
-+ //this.lastChunk = chunk; // Paper remove vanilla lastChunk
- return chunk;
- }
-
-@@ -198,7 +199,7 @@ public class ChunkProviderServer implements IChunkProvider {
- }
-
- this.chunks.put(k, chunk);
-- this.lastChunk = chunk;
-+ //this.lastChunk = chunk; // Paper
- }
-
- this.asyncTaskHandler.postToMainThread(chunk::addEntities);
-@@ -344,7 +345,7 @@ public class ChunkProviderServer implements IChunkProvider {
- this.saveChunk(chunk, true); // Spigot
- }
- this.chunks.remove(chunk.chunkKey);
-- this.lastChunk = null;
-+ // this.lastChunk = null; // Paper
- }
- return true;
- }
-@@ -380,6 +381,6 @@ public class ChunkProviderServer implements IChunkProvider {
- }
-
- public boolean isLoaded(int i, int j) {
-- return this.chunks.containsKey(ChunkCoordIntPair.a(i, j));
-+ return this.chunks.get(ChunkCoordIntPair.asLong(i, j)) != null; // Paper - use get for last access
- }
- }
---
-2.21.0
-
diff --git a/patches/removed/1.14/0096-Don-t-spam-reload-spawn-chunks-in-nether-end.patch b/patches/removed/1.14/0096-Don-t-spam-reload-spawn-chunks-in-nether-end.patch
deleted file mode 100644
index 382b025f1e..0000000000
--- a/patches/removed/1.14/0096-Don-t-spam-reload-spawn-chunks-in-nether-end.patch
+++ /dev/null
@@ -1,34 +0,0 @@
-From 2c462895126242c15395ff2033688c432492ad50 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Tue, 5 Apr 2016 19:42:22 -0400
-Subject: [PATCH] Don't spam reload spawn chunks in nether/end
-
-
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index b595536648..5fc2da0d92 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -2849,6 +2849,7 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- return this.K;
- }
-
-+ public boolean isSpawnChunk(int i, int j) { return e(i, j); } // Paper - OBFHELPER
- public boolean e(int i, int j) {
- BlockPosition blockposition = this.getSpawn();
- int k = i * 16 + 8 - blockposition.getX();
-diff --git a/src/main/java/net/minecraft/server/WorldProvider.java b/src/main/java/net/minecraft/server/WorldProvider.java
-index 5e87e537e4..3911e4947e 100644
---- a/src/main/java/net/minecraft/server/WorldProvider.java
-+++ b/src/main/java/net/minecraft/server/WorldProvider.java
-@@ -69,7 +69,7 @@ public abstract class WorldProvider {
- public void l() {}
-
- public boolean a(int i, int j) {
-- return !this.b.isForceLoaded(i, j);
-+ return !this.b.isSpawnChunk(i, j) && !this.b.isForceLoaded(i, j); // Paper - Use spawn chunks check for all worlds
- }
-
- protected abstract void m();
---
-2.21.0
-
diff --git a/patches/removed/1.14/0112-Entity-Tracking-Improvements.patch b/patches/removed/1.14/0112-Entity-Tracking-Improvements.patch
deleted file mode 100644
index a45d7f6192..0000000000
--- a/patches/removed/1.14/0112-Entity-Tracking-Improvements.patch
+++ /dev/null
@@ -1,103 +0,0 @@
-From 6d6e37ba0e3e91d7a711bc05c53daeeb304b82f4 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Mon, 17 Jun 2013 01:24:00 -0400
-Subject: [PATCH] Entity Tracking Improvements
-
-If any part of a Vehicle/Passenger relationship is visible to a player,
-send all passenger/vehicles to the player in the chain.
-
-diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
-index c9b37727ff..82994db643 100644
---- a/src/main/java/net/minecraft/server/Entity.java
-+++ b/src/main/java/net/minecraft/server/Entity.java
-@@ -70,6 +70,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
- public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper
- protected CraftEntity bukkitEntity;
-
-+ EntityTrackerEntry tracker; // Paper
- public CraftEntity getBukkitEntity() {
- if (bukkitEntity == null) {
- bukkitEntity = CraftEntity.getEntity(world.getServer(), this);
-diff --git a/src/main/java/net/minecraft/server/EntityTrackerEntry.java b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
-index de0cf6b735..5629f9909b 100644
---- a/src/main/java/net/minecraft/server/EntityTrackerEntry.java
-+++ b/src/main/java/net/minecraft/server/EntityTrackerEntry.java
-@@ -56,6 +56,7 @@ public class EntityTrackerEntry {
- // Paper end
-
- public EntityTrackerEntry(Entity entity, int i, int j, int k, boolean flag) {
-+ entity.tracker = this; // Paper
- this.tracker = entity;
- this.e = i;
- this.f = j;
-@@ -453,17 +454,59 @@ public class EntityTrackerEntry {
-
- this.tracker.b(entityplayer);
- entityplayer.d(this.tracker);
-+ updatePassengers(entityplayer); // Paper
- }
- } else if (this.trackedPlayers.contains(entityplayer)) {
- this.trackedPlayers.remove(entityplayer);
- this.tracker.c(entityplayer);
- entityplayer.c(this.tracker);
-+ updatePassengers(entityplayer); // Paper
- }
-
- }
- }
-
- public boolean c(EntityPlayer entityplayer) {
-+ // Paper start
-+ if (tracker.isPassenger()) {
-+ return isTrackedBy(tracker.getVehicle(), entityplayer);
-+ } else if (hasPassengerInRange(tracker, entityplayer)) {
-+ return true;
-+ }
-+
-+ return isInRangeOfPlayer(entityplayer);
-+ }
-+ private static boolean hasPassengerInRange(Entity entity, EntityPlayer entityplayer) {
-+ if (!entity.isVehicle()) {
-+ return false;
-+ }
-+ for (Entity passenger : entity.passengers) {
-+ if (passenger.tracker != null && passenger.tracker.isInRangeOfPlayer(entityplayer)) {
-+ return true;
-+ }
-+ if (passenger.isVehicle()) {
-+ if (hasPassengerInRange(passenger, entityplayer)) {
-+ return true;
-+ }
-+ }
-+ }
-+ return false;
-+ }
-+ private static boolean isTrackedBy(Entity entity, EntityPlayer entityplayer) {
-+ return entity == entityplayer || entity.tracker != null && entity.tracker.trackedPlayers.contains(entityplayer);
-+ }
-+ private void updatePassengers(EntityPlayer player) {
-+ if (tracker.isVehicle()) {
-+ tracker.passengers.forEach((e) -> {
-+ if (e.tracker != null) {
-+ e.tracker.updatePlayer(player);
-+ }
-+ });
-+ player.playerConnection.sendPacket(new PacketPlayOutMount(this.tracker));
-+ }
-+ }
-+ private boolean isInRangeOfPlayer(EntityPlayer entityplayer) {
-+ // Paper end
- double d0 = entityplayer.locX - (double) this.xLoc / 4096.0D;
- double d1 = entityplayer.locZ - (double) this.zLoc / 4096.0D;
- int i = Math.min(this.e, (entityplayer.getViewDistance() - 1) * 16); // Paper - Use player view distance API
-@@ -604,6 +647,7 @@ public class EntityTrackerEntry {
- this.trackedPlayers.remove(entityplayer);
- this.tracker.c(entityplayer);
- entityplayer.c(this.tracker);
-+ updatePassengers(entityplayer); // Paper
- }
-
- }
---
-2.21.0
-
diff --git a/patches/removed/1.14/0120-Ensure-Chunks-never-ever-load-async.patch b/patches/removed/1.14/0120-Ensure-Chunks-never-ever-load-async.patch
deleted file mode 100644
index d43e519a74..0000000000
--- a/patches/removed/1.14/0120-Ensure-Chunks-never-ever-load-async.patch
+++ /dev/null
@@ -1,47 +0,0 @@
-From 036c518050a0972307568a014156d59bb90bf54c Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Fri, 27 May 2016 21:41:26 -0400
-Subject: [PATCH] Ensure Chunks never ever load async
-
-Safely pushes the operation to main thread, then back to the posting thread
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java
-index e4fd9bc604..7ffb8f6172 100644
---- a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java
-+++ b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOExecutor.java
-@@ -3,6 +3,7 @@ package org.bukkit.craftbukkit.chunkio;
- import net.minecraft.server.Chunk;
- import net.minecraft.server.ChunkProviderServer;
- import net.minecraft.server.ChunkRegionLoader;
-+import net.minecraft.server.MCUtil; // Paper
- import net.minecraft.server.World;
- import org.bukkit.craftbukkit.util.AsynchronousExecutor;
-
-@@ -13,7 +14,7 @@ public class ChunkIOExecutor {
- private static final AsynchronousExecutor<QueuedChunk, Chunk, Runnable, RuntimeException> instance = new AsynchronousExecutor<QueuedChunk, Chunk, Runnable, RuntimeException>(new ChunkIOProvider(), BASE_THREADS);
-
- public static Chunk syncChunkLoad(World world, ChunkRegionLoader loader, ChunkProviderServer provider, int x, int z) {
-- return instance.getSkipQueue(new QueuedChunk(x, z, loader, world, provider));
-+ return MCUtil.ensureMain("Async Chunk Load", () -> instance.getSkipQueue(new QueuedChunk(x, z, loader, world, provider))); // Paper
- }
-
- public static void queueChunkLoad(World world, ChunkRegionLoader loader, ChunkProviderServer provider, int x, int z, Runnable runnable) {
-diff --git a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java
-index 52a8c48fa4..4cfe24df15 100644
---- a/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java
-+++ b/src/main/java/org/bukkit/craftbukkit/chunkio/ChunkIOProvider.java
-@@ -35,9 +35,9 @@ class ChunkIOProvider implements AsynchronousExecutor.CallBackProvider<QueuedChu
-
- // sync stuff
- public void callStage2(QueuedChunk queuedChunk, Chunk chunk) throws RuntimeException {
-- if (chunk == null) {
-+ if (chunk == null || queuedChunk.provider.chunks.containsKey(ChunkCoordIntPair.a(queuedChunk.x, queuedChunk.z))) { // Paper - also call original if it was already loaded
- // If the chunk loading failed just do it synchronously (may generate)
-- // queuedChunk.provider.originalGetChunkAt(queuedChunk.x, queuedChunk.z);
-+ queuedChunk.provider.getChunkAt(queuedChunk.x, queuedChunk.z, true, true); // Paper - actually call original if it was already loaded
- return;
- }
- try (Timing ignored = queuedChunk.provider.world.timings.chunkIOStage2.startTimingIfSync()) { // Paper
---
-2.21.0
-
diff --git a/patches/removed/1.14/0123-Delay-Chunk-Unloads-based-on-Player-Movement.patch b/patches/removed/1.14/0123-Delay-Chunk-Unloads-based-on-Player-Movement.patch
deleted file mode 100755
index 33d5985c69..0000000000
--- a/patches/removed/1.14/0123-Delay-Chunk-Unloads-based-on-Player-Movement.patch
+++ /dev/null
@@ -1,224 +0,0 @@
-From 2fbf2c556d7840198741959940696a14c7e3c26a Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 18 Jun 2016 23:22:12 -0400
-Subject: [PATCH] Delay Chunk Unloads based on Player Movement
-
-When players are moving in the world, doing things such as building or exploring,
-they will commonly go back and forth in a small area. This causes a ton of chunk load
-and unload activity on the edge chunks of their view distance.
-
-A simple back and forth movement in 6 blocks could spam a chunk to thrash a
-loading and unload cycle over and over again.
-
-This is very wasteful. This system introduces a delay of inactivity on a chunk
-before it actually unloads, which is maintained separately from ChunkGC.
-
-This allows servers with smaller worlds who do less long distance exploring to stop
-wasting cpu cycles on saving/unloading/reloading chunks repeatedly.
-
-This also makes the Chunk GC System useless, by auto scheduling unload as soon as
-a spare chunk is added to the server thats outside of view distance.
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index bcc2ecaa3a..c70771614d 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -310,4 +310,18 @@ public class PaperWorldConfig {
- preventTntFromMovingInWater = getBoolean("prevent-tnt-from-moving-in-water", false);
- log("Prevent TNT from moving in water: " + preventTntFromMovingInWater);
- }
-+
-+ public long delayChunkUnloadsBy;
-+ private void delayChunkUnloadsBy() {
-+ delayChunkUnloadsBy = PaperConfig.getSeconds(getString("delay-chunk-unloads-by", "10s"));
-+ if (delayChunkUnloadsBy > 0) {
-+ log("Delaying chunk unloads by " + delayChunkUnloadsBy + " seconds");
-+ delayChunkUnloadsBy *= 1000;
-+ }
-+ }
-+
-+ public boolean skipEntityTickingInChunksScheduledForUnload = true;
-+ private void skipEntityTickingInChunksScheduledForUnload() {
-+ skipEntityTickingInChunksScheduledForUnload = getBoolean("skip-entity-ticking-in-chunks-scheduled-for-unload", skipEntityTickingInChunksScheduledForUnload);
-+ }
- }
-diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
-index c74176daa5..bdf922db50 100644
---- a/src/main/java/net/minecraft/server/Chunk.java
-+++ b/src/main/java/net/minecraft/server/Chunk.java
-@@ -40,6 +40,7 @@ public class Chunk implements IChunkAccess {
- private boolean i;public boolean isLoaded() { return i; } // Paper - OBFHELPER
- public final World world;
- public final Map<HeightMap.Type, HeightMap> heightMap;
-+ public Long scheduledForUnload; // Paper - delay chunk unloads
- public final int locX;
- public final int locZ;
- private boolean l;
-diff --git a/src/main/java/net/minecraft/server/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java
-index 8b3738c8f7..2021c0d02e 100644
---- a/src/main/java/net/minecraft/server/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/ChunkMap.java
-@@ -48,6 +48,15 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- }
- }
- }
-+ // Paper start - if this is a spare chunk (not part of any players view distance), go ahead and queue it for unload.
-+ if (!((WorldServer)chunk.world).getPlayerChunkMap().isChunkInUse(chunk.locX, chunk.locZ)) {
-+ if (chunk.world.paperConfig.delayChunkUnloadsBy > 0) {
-+ chunk.scheduledForUnload = System.currentTimeMillis();
-+ } else {
-+ ((WorldServer) chunk.world).getChunkProvider().unload(chunk);
-+ }
-+ }
-+ // Paper end
- chunk.world.timings.syncChunkLoadPostTimer.stopTiming(); // Paper
- // CraftBukkit end
-
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-index c54df45837..d0bf0f72da 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-@@ -307,6 +307,19 @@ public class ChunkProviderServer implements IChunkProvider {
- }
- activityAccountant.endActivity(); // Spigot
- }
-+ // Paper start - delayed chunk unloads
-+ long now = System.currentTimeMillis();
-+ long unloadAfter = world.paperConfig.delayChunkUnloadsBy;
-+ if (unloadAfter > 0) {
-+ //noinspection Convert2streamapi
-+ for (Chunk chunk : chunks.values()) {
-+ if (chunk.scheduledForUnload != null && now - chunk.scheduledForUnload > unloadAfter) {
-+ chunk.scheduledForUnload = null;
-+ unload(chunk);
-+ }
-+ }
-+ }
-+ // Paper end
-
- this.chunkScheduler.a(booleansupplier);
- }
-diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
-index e47aae2f8b..b9d90c4fb8 100644
---- a/src/main/java/net/minecraft/server/PlayerChunk.java
-+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
-@@ -29,8 +29,23 @@ public class PlayerChunk {
-
- chunkproviderserver.a(i, j);
- this.chunk = chunkproviderserver.getChunkAt(i, j, true, false);
-+ markChunkUsed(); // Paper - delay chunk unloads
- }
-
-+ // Paper start
-+ private void markChunkUsed() {
-+ if (chunk == null) {
-+ return;
-+ }
-+ if (chunkHasPlayers) {
-+ chunk.scheduledForUnload = null;
-+ } else if (chunk.scheduledForUnload == null) {
-+ chunk.scheduledForUnload = System.currentTimeMillis();
-+ }
-+ }
-+ private boolean chunkHasPlayers = false;
-+ // Paper end
-+
- public ChunkCoordIntPair a() {
- return this.location;
- }
-@@ -41,6 +56,8 @@ public class PlayerChunk {
- } else {
- if (this.players.isEmpty()) {
- this.i = this.playerChunkMap.getWorld().getTime();
-+ chunkHasPlayers = true; // Paper - delay chunk unloads
-+ markChunkUsed(); // Paper - delay chunk unloads
- }
-
- this.players.add(entityplayer);
-@@ -59,6 +76,8 @@ public class PlayerChunk {
-
- this.players.remove(entityplayer);
- if (this.players.isEmpty()) {
-+ chunkHasPlayers = false; // Paper - delay chunk unloads
-+ markChunkUsed(); // Paper - delay chunk unloads
- this.playerChunkMap.b(this);
- }
-
-@@ -70,6 +89,7 @@ public class PlayerChunk {
- return true;
- } else {
- this.chunk = this.playerChunkMap.getWorld().getChunkProvider().getChunkAt(this.location.x, this.location.z, true, flag);
-+ markChunkUsed(); // Paper - delay chunk unloads
- return this.chunk != null;
- }
- }
-diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
-index 2064576501..ab4f3b7223 100644
---- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
-+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
-@@ -452,7 +452,13 @@ public class PlayerChunkMap {
- Chunk chunk = playerchunk.f();
-
- if (chunk != null) {
-- this.getWorld().getChunkProvider().unload(chunk);
-+ // Paper start - delay chunk unloads
-+ if (world.paperConfig.delayChunkUnloadsBy <= 0) {
-+ this.getWorld().getChunkProvider().unload(chunk);
-+ } else {
-+ chunk.scheduledForUnload = System.currentTimeMillis();
-+ }
-+ // Paper end
- }
-
- }
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index 32ee298648..dcff6c8d8a 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -1293,7 +1293,13 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- if (!tileentity.x() && tileentity.hasWorld()) {
- BlockPosition blockposition = tileentity.getPosition();
-
-- if (this.isLoaded(blockposition) && this.K.a(blockposition)) {
-+ // Paper start - Skip ticking in chunks scheduled for unload
-+ net.minecraft.server.Chunk chunk = this.getChunkIfLoaded(blockposition);
-+ boolean shouldTick = chunk != null;
-+ if(this.paperConfig.skipEntityTickingInChunksScheduledForUnload)
-+ shouldTick = shouldTick && chunk.scheduledForUnload == null;
-+ if (shouldTick && this.K.a(blockposition)) {
-+ // Paper end
- try {
- this.methodProfiler.a(() -> {
- return String.valueOf(TileEntityTypes.a(tileentity.C()));
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index a8c7e7931e..f7883e7085 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -1772,7 +1772,7 @@ public class CraftWorld implements World {
- ChunkProviderServer cps = world.getChunkProvider();
- for (net.minecraft.server.Chunk chunk : cps.chunks.values()) {
- // If in use, skip it
-- if (isChunkInUse(chunk.locX, chunk.locZ)) {
-+ if (isChunkInUse(chunk.locX, chunk.locZ) || chunk.scheduledForUnload != null) { // Paper - delayed chunk unloads
- continue;
- }
-
-diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
-index d08ef3fe10..081789a8fe 100644
---- a/src/main/java/org/spigotmc/ActivationRange.java
-+++ b/src/main/java/org/spigotmc/ActivationRange.java
-@@ -323,6 +323,11 @@ public class ActivationRange
- {
- isActive = false;
- }
-+ // Paper start - Skip ticking in chunks scheduled for unload
-+ else if (entity.world.paperConfig.skipEntityTickingInChunksScheduledForUnload && (chunk == null || chunk.scheduledForUnload != null)) {
-+ isActive = false;
-+ }
-+ // Paper end
- return isActive;
- }
- }
---
-2.21.0
-
diff --git a/patches/removed/1.14/0142-Prevent-Auto-Save-if-Save-Queue-is-full.patch b/patches/removed/1.14/0142-Prevent-Auto-Save-if-Save-Queue-is-full.patch
deleted file mode 100644
index 359e6e63d6..0000000000
--- a/patches/removed/1.14/0142-Prevent-Auto-Save-if-Save-Queue-is-full.patch
+++ /dev/null
@@ -1,58 +0,0 @@
-From 266fe023d1d38728ba461724a7f2a23a2ee07cfd Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Thu, 3 Nov 2016 21:52:22 -0400
-Subject: [PATCH] Prevent Auto Save if Save Queue is full
-
-If the save queue already has 50 (configurable) of chunks pending,
-then avoid processing auto save (which would add more)
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index 7e847af00b..9829b3b64b 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -341,6 +341,11 @@ public class PaperWorldConfig {
- maxAutoSaveChunksPerTick = getInt("max-auto-save-chunks-per-tick", 24);
- }
-
-+ public int queueSizeAutoSaveThreshold = 50;
-+ private void queueSizeAutoSaveThreshold() {
-+ queueSizeAutoSaveThreshold = getInt("save-queue-limit-for-auto-save", 50);
-+ }
-+
- public boolean removeCorruptTEs = false;
- private void removeCorruptTEs() {
- removeCorruptTEs = getBoolean("remove-corrupt-tile-entities", false);
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-index fbc69b5ba5..9b5908a5b4 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-@@ -236,6 +236,13 @@ public class ChunkProviderServer implements IChunkProvider {
- synchronized (this.chunkLoader) {
- ObjectIterator objectiterator = this.chunks.values().iterator();
-
-+ // Paper start
-+ final ChunkRegionLoader chunkLoader = (ChunkRegionLoader) world.getChunkProvider().chunkLoader;
-+ final int queueSize = chunkLoader.getQueueSize();
-+ if (!flag && queueSize > world.paperConfig.queueSizeAutoSaveThreshold){
-+ return false;
-+ }
-+ // Paper end
- while (objectiterator.hasNext()) {
- Chunk chunk = (Chunk) objectiterator.next();
-
-diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-index a144118f66..adfb5d056f 100644
---- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-@@ -156,6 +156,8 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
-
- }
-
-+ public int getQueueSize() { return queue.size(); } // Paper
-+
- // CraftBukkit start - Add async variant, provide compatibility
- @Nullable
- public Chunk a(GeneratorAccess generatoraccess, int i, int j, Consumer<Chunk> consumer) throws IOException {
---
-2.21.0
-
diff --git a/patches/removed/1.14/0143-Chunk-Save-Stats-Debug-Option.patch b/patches/removed/1.14/0143-Chunk-Save-Stats-Debug-Option.patch
deleted file mode 100644
index ea8f050588..0000000000
--- a/patches/removed/1.14/0143-Chunk-Save-Stats-Debug-Option.patch
+++ /dev/null
@@ -1,93 +0,0 @@
-From 8efbb3a0533a16d6c9e3ac27e41cd03bba1e35d8 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Fri, 4 Nov 2016 02:12:10 -0400
-Subject: [PATCH] Chunk Save Stats Debug Option
-
-Adds a command line flag to enable stats on how chunk saves are processing.
-
-Stats on current queue, how many was processed and how many were queued.
-
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-index 9b5908a5b4..2997767282 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-@@ -28,6 +28,11 @@ public class ChunkProviderServer implements IChunkProvider {
- public final LongSet unloadQueue = new LongOpenHashSet();
- public final ChunkGenerator<?> chunkGenerator;
- public final IChunkLoader chunkLoader;
-+ // Paper start - chunk save stats
-+ private long lastQueuedSaves = 0L; // Paper
-+ private long lastProcessedSaves = 0L; // Paper
-+ private long lastSaveStatPrinted = System.currentTimeMillis();
-+ // Paper end
- public final Long2ObjectMap<Chunk> chunks = Long2ObjectMaps.synchronize(new ChunkMap(8192));
- private Chunk lastChunk;
- private final ChunkTaskScheduler chunkScheduler;
-@@ -239,6 +244,29 @@ public class ChunkProviderServer implements IChunkProvider {
- // Paper start
- final ChunkRegionLoader chunkLoader = (ChunkRegionLoader) world.getChunkProvider().chunkLoader;
- final int queueSize = chunkLoader.getQueueSize();
-+
-+ final long now = System.currentTimeMillis();
-+ final long timeSince = (now - lastSaveStatPrinted) / 1000;
-+ final Integer printRateSecs = Integer.getInteger("printSaveStats");
-+ if (printRateSecs != null && timeSince >= printRateSecs) {
-+ final String timeStr = "/" + timeSince +"s";
-+ final long queuedSaves = chunkLoader.getQueuedSaves();
-+ long queuedDiff = queuedSaves - lastQueuedSaves;
-+ lastQueuedSaves = queuedSaves;
-+
-+ final long processedSaves = chunkLoader.getProcessedSaves();
-+ long processedDiff = processedSaves - lastProcessedSaves;
-+ lastProcessedSaves = processedSaves;
-+
-+ lastSaveStatPrinted = now;
-+ if (processedDiff > 0 || queueSize > 0 || queuedDiff > 0) {
-+ System.out.println("[Chunk Save Stats] " + world.worldData.getName() +
-+ " - Current: " + queueSize +
-+ " - Queued: " + queuedDiff + timeStr +
-+ " - Processed: " +processedDiff + timeStr
-+ );
-+ }
-+ }
- if (!flag && queueSize > world.paperConfig.queueSizeAutoSaveThreshold){
- return false;
- }
-diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-index adfb5d056f..0fc4d9f520 100644
---- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-@@ -156,7 +156,13 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
-
- }
-
-- public int getQueueSize() { return queue.size(); } // Paper
-+ // Paper start
-+ private long queuedSaves = 0;
-+ private final java.util.concurrent.atomic.AtomicLong processedSaves = new java.util.concurrent.atomic.AtomicLong(0L);
-+ public int getQueueSize() { return queue.size(); }
-+ public long getQueuedSaves() { return queuedSaves; }
-+ public long getProcessedSaves() { return processedSaves.longValue(); }
-+ // Paper end
-
- // CraftBukkit start - Add async variant, provide compatibility
- @Nullable
-@@ -348,6 +354,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- protected void a(ChunkCoordIntPair chunkcoordintpair, Supplier<NBTTagCompound> nbttagcompound) { // Spigot
- this.saveMap.put(chunkcoordintpair.asLong(), nbttagcompound); // Paper
- queue.add(new QueuedChunk(chunkcoordintpair, nbttagcompound)); // Paper - Chunk queue improvements
-+ queuedSaves++; // Paper
- FileIOThread.a().a(this);
- }
-
-@@ -375,6 +382,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- // Paper end
- ChunkCoordIntPair chunkcoordintpair = chunk.coords; // Paper - Chunk queue improvements
- Supplier<NBTTagCompound> nbttagcompound = chunk.compoundSupplier; // Spigot // Paper
-+ processedSaves.incrementAndGet(); // Paper
-
- if (nbttagcompound == null) {
- return true;
---
-2.21.0
-
diff --git a/patches/removed/1.14/0144-Fix-block-break-desync.patch b/patches/removed/1.14/0144-Fix-block-break-desync.patch
deleted file mode 100644
index 36562a4c0c..0000000000
--- a/patches/removed/1.14/0144-Fix-block-break-desync.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-From 635cfbbdad0af0805e7eadf3de564462628d732f Mon Sep 17 00:00:00 2001
-From: Michael Himing <[email protected]>
-Date: Sun, 8 Jan 2017 18:50:35 +1100
-Subject: [PATCH] Fix block break desync
-
-
-diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java
-index fd8d30e5cb..b34a0fb350 100644
---- a/src/main/java/net/minecraft/server/PlayerConnection.java
-+++ b/src/main/java/net/minecraft/server/PlayerConnection.java
-@@ -1185,6 +1185,8 @@ public class PlayerConnection implements PacketListenerPlayIn {
- double d3 = d0 * d0 + d1 * d1 + d2 * d2;
-
- if (d3 > 36.0D) {
-+ if (worldserver.isChunkLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4)) // Paper - Fix block break desync - Don't send for unloaded chunks
-+ this.sendPacket(new PacketPlayOutBlockChange(worldserver, blockposition)); // Paper - Fix block break desync
- return;
- } else if (blockposition.getY() >= this.minecraftServer.getMaxBuildHeight()) {
- return;
---
-2.22.0
-
diff --git a/patches/removed/1.14/0161-ShulkerBox-Dupe-Prevention.patch b/patches/removed/1.14/0161-ShulkerBox-Dupe-Prevention.patch
deleted file mode 100755
index cc798de166..0000000000
--- a/patches/removed/1.14/0161-ShulkerBox-Dupe-Prevention.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 16ba1e065d52597408a18d8bbb9fa3686271fc6a Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Mon, 2 Jan 2017 16:32:56 -0500
-Subject: [PATCH] ShulkerBox Dupe Prevention
-
-This ensures that Shulker Boxes can never drop their contents twice, and
-that the inventory is cleared incase it some how also got saved to the world.
-
-diff --git a/src/main/java/net/minecraft/server/BlockShulkerBox.java b/src/main/java/net/minecraft/server/BlockShulkerBox.java
-index ab0ece557c..997ed795b1 100644
---- a/src/main/java/net/minecraft/server/BlockShulkerBox.java
-+++ b/src/main/java/net/minecraft/server/BlockShulkerBox.java
-@@ -100,6 +100,7 @@ public class BlockShulkerBox extends BlockTileEntity {
- }
-
- a(world, blockposition, itemstack);
-+ tileentityshulkerbox.clear(); // Paper - This was intended to be called in Vanilla (is checked in the if statement above if has been called) - Fixes dupe issues
- }
- }
- world.updateAdjacentComparators(blockposition, iblockdata.getBlock());
---
-2.21.0
-
diff --git a/patches/removed/1.14/0258-Configurable-Villages-loading-chunks-for-door-checks.patch b/patches/removed/1.14/0258-Configurable-Villages-loading-chunks-for-door-checks.patch
deleted file mode 100644
index af47335922..0000000000
--- a/patches/removed/1.14/0258-Configurable-Villages-loading-chunks-for-door-checks.patch
+++ /dev/null
@@ -1,128 +0,0 @@
-From 1648e767bbdfe53802b2c3a864c74cdaaca6909e Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Tue, 5 Jun 2018 00:32:22 -0400
-Subject: [PATCH] Configurable Villages loading chunks for door checks
-
-This avoids villages spam loading chunks sync.
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index 0a270b899d..4727ac6eb5 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -434,4 +434,12 @@ public class PaperWorldConfig {
- disableEnderpearlExploit = getBoolean("game-mechanics.disable-unloaded-chunk-enderpearl-exploit", disableEnderpearlExploit);
- log("Disable Unloaded Chunk Enderpearl Exploit: " + (disableEnderpearlExploit ? "enabled" : "disabled"));
- }
-+
-+ public boolean villagesLoadChunks = false;
-+ private void villagesLoadChunks() {
-+ villagesLoadChunks = getBoolean("game-mechanics.villages-load-chunks", false);
-+ if (villagesLoadChunks) {
-+ log("Villages can load chunks - Warning this can cause intense TPS loss. Strongly consider disabling this.");
-+ }
-+ }
- }
-diff --git a/src/main/java/net/minecraft/server/PersistentVillage.java b/src/main/java/net/minecraft/server/PersistentVillage.java
-index 7a9fb97530..e40cd41869 100644
---- a/src/main/java/net/minecraft/server/PersistentVillage.java
-+++ b/src/main/java/net/minecraft/server/PersistentVillage.java
-@@ -136,7 +136,7 @@ public class PersistentVillage extends PersistentBase {
- for (int j = -4; j < 4; ++j) {
- for (int k = -16; k < 16; ++k) {
- blockposition_mutableblockposition.g(blockposition).d(i, j, k);
-- IBlockData iblockdata = this.world.getType(blockposition_mutableblockposition);
-+ IBlockData iblockdata = this.world.paperConfig.villagesLoadChunks ? this.world.getType(blockposition_mutableblockposition) : this.world.getTypeIfLoaded(blockposition_mutableblockposition); // Paper
-
- if (this.a(iblockdata)) {
- VillageDoor villagedoor = this.c(blockposition_mutableblockposition);
-@@ -228,7 +228,7 @@ public class PersistentVillage extends PersistentBase {
- }
-
- private boolean a(IBlockData iblockdata) {
-- return iblockdata.getBlock() instanceof BlockDoor && iblockdata.getMaterial() == Material.WOOD;
-+ return iblockdata != null && iblockdata.getBlock() instanceof BlockDoor && iblockdata.getMaterial() == Material.WOOD; // Paper
- }
-
- public void a(NBTTagCompound nbttagcompound) {
-diff --git a/src/main/java/net/minecraft/server/Village.java b/src/main/java/net/minecraft/server/Village.java
-index b794572915..1363c53ff0 100644
---- a/src/main/java/net/minecraft/server/Village.java
-+++ b/src/main/java/net/minecraft/server/Village.java
-@@ -11,10 +11,10 @@ import javax.annotation.Nullable;
-
- public class Village {
-
-- private World a;
-+ private World a; private World getWorld() { return a; } // Paper - OBFHELPER
- private final List<VillageDoor> b = Lists.newArrayList();
- private BlockPosition c;
-- private BlockPosition d;
-+ private BlockPosition d;private BlockPosition getCenter() { return d; } // Paper - OBFHELPER
- private int e;
- private int f;
- private int g;
-@@ -44,6 +44,12 @@ public class Village {
- }
-
- public void a(int i) {
-+ // Paper - don't tick village if chunk isn't loaded
-+ Chunk chunk = getWorld().getChunkIfLoaded(getCenter());
-+ if (chunk == null || !chunk.areNeighborsLoaded(1)) {
-+ return;
-+ }
-+ // Paper end
- this.g = i;
- this.m();
- this.l();
-@@ -292,6 +298,12 @@ public class Village {
-
- while (iterator.hasNext()) {
- VillageDoor villagedoor = (VillageDoor) iterator.next();
-+ // Paper start - don't remove doors from unloaded chunks
-+ if (!getWorld().isLoaded(villagedoor.getPosition())) {
-+ villagedoor.setLastSeen(villagedoor.getLastSeen() + 1);
-+ continue;
-+ }
-+ // Paper end
-
- if (flag1) {
- villagedoor.a();
-@@ -312,7 +324,9 @@ public class Village {
- }
-
- private boolean g(BlockPosition blockposition) {
-- IBlockData iblockdata = this.a.getType(blockposition);
-+ IBlockData iblockdata = this.a.paperConfig.villagesLoadChunks ? this.a.getType(blockposition) : this.a.getTypeIfLoaded(blockposition); // Paper
-+ if (iblockdata == null) return false; // Paper
-+
- Block block = iblockdata.getBlock();
-
- return block instanceof BlockDoor ? iblockdata.getMaterial() == Material.WOOD : false;
-diff --git a/src/main/java/net/minecraft/server/VillageDoor.java b/src/main/java/net/minecraft/server/VillageDoor.java
-index 33ad5faa3e..1edffc4629 100644
---- a/src/main/java/net/minecraft/server/VillageDoor.java
-+++ b/src/main/java/net/minecraft/server/VillageDoor.java
-@@ -55,6 +55,7 @@ public class VillageDoor {
- return this.f;
- }
-
-+ public BlockPosition getPosition() { return d(); } // Paper - OBFHELPER
- public BlockPosition d() {
- return this.a;
- }
-@@ -71,10 +72,12 @@ public class VillageDoor {
- return this.c.getAdjacentZ() * 2;
- }
-
-+ public int getLastSeen() { return h(); } // Paper - OBFHELPER
- public int h() {
- return this.d;
- }
-
-+ public void setLastSeen(int i) { a(i); } // Paper - OBFHELPER
- public void a(int i) {
- this.d = i;
- }
---
-2.21.0
-
diff --git a/patches/removed/1.14/0263-Properly-remove-entities-on-dimension-teleport.patch b/patches/removed/1.14/0263-Properly-remove-entities-on-dimension-teleport.patch
deleted file mode 100644
index 6231cf6b35..0000000000
--- a/patches/removed/1.14/0263-Properly-remove-entities-on-dimension-teleport.patch
+++ /dev/null
@@ -1,51 +0,0 @@
-From 8a4b7bff6950703ef13ea7ce72e80af4cee67d13 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sun, 10 Jun 2018 20:04:42 -0400
-Subject: [PATCH] Properly remove entities on dimension teleport
-
-To teleport an entity between dimensions, the server makes a copy
-and puts the copy in the new location, and marks the old one dead.
-
-If this method got called for the same world in the same tick,
-the entity would not have been removed from the UUID map, and the
-world readd would fail.
-
-This can be triggered even with a plugin if the entity is teleported
-twice in the same tick, from world A to B, then back from B to A.
-
-The re-add to A will fail to add the entity to the world. It will
-actually be there, but it will not be visible on the client until
-the server is restarted to re-try the add to world process again.
-
-This bug was unlikely to be seen by many due to the double teleport
-requirement, but plugins (such as my own) use this method to
-trigger a "reload" of the entity on the client.
-
-diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
-index 92a15ba947..debab1a715 100644
---- a/src/main/java/net/minecraft/server/Entity.java
-+++ b/src/main/java/net/minecraft/server/Entity.java
-@@ -2611,7 +2611,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
- }
- // CraftBukkit end */
-
-- this.world.kill(this);
-+ this.world.removeEntity(this); // Paper - Fully remove entity, can't have dupes in the UUID map
- this.dead = false;
- this.world.methodProfiler.enter("reposition");
- /* CraftBukkit start - Handled in calculateTarget
-diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
-index e4730352d3..c08ee62e84 100644
---- a/src/main/java/net/minecraft/server/WorldServer.java
-+++ b/src/main/java/net/minecraft/server/WorldServer.java
-@@ -997,6 +997,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
- }
-
- protected void c(Entity entity) {
-+ if (!this.entitiesByUUID.containsKey(entity.getUniqueID()) && !entity.valid) return; // Paper - Already removed, dont fire twice - this looks like it can happen even without our changes
- super.c(entity);
- this.entitiesById.d(entity.getId());
- this.entitiesByUUID.remove(entity.getUniqueID());
---
-2.21.0
-
diff --git a/patches/removed/1.14/0285-Don-t-process-despawn-if-entity-is-in-a-chunk-schedu.patch b/patches/removed/1.14/0285-Don-t-process-despawn-if-entity-is-in-a-chunk-schedu.patch
deleted file mode 100644
index a52a446c32..0000000000
--- a/patches/removed/1.14/0285-Don-t-process-despawn-if-entity-is-in-a-chunk-schedu.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-From 349376a18c6a86ab6bb07513849d403ab6c71669 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Thu, 19 Jul 2018 01:23:00 -0400
-Subject: [PATCH] Don't process despawn if entity is in a chunk scheduled for
- unload
-
-This won't happen anyways if the user has
-"skip ticking for entities in chunks scheduled for unload" turned on,
-but if they don't, protect from this instant killing the entity to
-keep it vanilla in behavior
-
-a player may teleport away, and trigger instant despawn
-
-diff --git a/src/main/java/net/minecraft/server/EntityInsentient.java b/src/main/java/net/minecraft/server/EntityInsentient.java
-index 98e214cdd6..ee5078370c 100644
---- a/src/main/java/net/minecraft/server/EntityInsentient.java
-+++ b/src/main/java/net/minecraft/server/EntityInsentient.java
-@@ -634,6 +634,8 @@ public abstract class EntityInsentient extends EntityLiving {
- if (this.persistent) {
- this.ticksFarFromPlayer = 0;
- } else {
-+ Chunk currentChunk = getChunkAtLocation(); // Paper
-+ if (currentChunk != null && currentChunk.scheduledForUnload != null) return; // Paper
- EntityHuman entityhuman = this.world.findNearbyPlayer(this, -1.0D);
-
- if (entityhuman != null && entityhuman.affectsSpawning) { // Paper - Affects Spawning API
---
-2.21.0
-
diff --git a/patches/removed/1.14/0341-Optimize-getChunkIfLoaded-type-calls.patch b/patches/removed/1.14/0341-Optimize-getChunkIfLoaded-type-calls.patch
deleted file mode 100644
index a0b48be77e..0000000000
--- a/patches/removed/1.14/0341-Optimize-getChunkIfLoaded-type-calls.patch
+++ /dev/null
@@ -1,81 +0,0 @@
-From c630ddd10b4711ffcd5318829821fb44f48294df Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 29 Aug 2018 21:59:22 -0400
-Subject: [PATCH] Optimize getChunkIfLoaded type calls
-
-Uses optimized check to avoid major locks and large method.
-
-Will improve inlining across many hot methods.
-
-Improve getBrightness to not do double chunk map lookups.
-
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-index 41926a361b..186cfda7e4 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-@@ -379,7 +379,7 @@ public class ChunkProviderServer implements IChunkProvider {
- continue;
- }
-
-- Chunk neighbor = this.getChunkAt(chunk.locX + x, chunk.locZ + z, false, false);
-+ Chunk neighbor = this.chunks.get(chunk.chunkKey); // Paper
- if (neighbor != null) {
- neighbor.setNeighborUnloaded(-x, -z);
- chunk.setNeighborUnloaded(x, z);
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index 14f419deb8..630ebfb37c 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -162,7 +162,7 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- }
-
- public Chunk getChunkIfLoaded(int x, int z) {
-- return ((ChunkProviderServer) this.chunkProvider).getChunkAt(x, z, false, false);
-+ return ((ChunkProviderServer) this.chunkProvider).chunks.get(ChunkCoordIntPair.a(x, z)); // Paper - optimize getChunkIfLoaded
- }
-
- protected World(IDataManager idatamanager, @Nullable PersistentCollection persistentcollection, WorldData worlddata, WorldProvider worldprovider, MethodProfiler methodprofiler, boolean flag, ChunkGenerator gen, org.bukkit.World.Environment env) {
-@@ -724,7 +724,8 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- blockposition = new BlockPosition(blockposition.getX(), 0, blockposition.getZ());
- }
-
-- return !blockposition.isValidLocation() ? enumskyblock.c : (!this.isLoaded(blockposition) ? enumskyblock.c : this.getChunkAtWorldCoords(blockposition).getBrightness(enumskyblock, blockposition)); // Paper
-+ Chunk chunk; // Paper
-+ return !blockposition.isValidLocation() ? enumskyblock.c : ((chunk = this.getChunkIfLoaded(blockposition)) == null ? enumskyblock.c : chunk.getBrightness(enumskyblock, blockposition)); // Paper - optimize ifChunkLoaded
- }
-
- public void a(EnumSkyBlock enumskyblock, BlockPosition blockposition, int i) {
-@@ -1959,7 +1960,7 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- if (blockposition.isInvalidYLocation()) { // Paper
- return false;
- } else {
-- Chunk chunk = this.chunkProvider.getChunkAt(blockposition.getX() >> 4, blockposition.getZ() >> 4, false, false);
-+ Chunk chunk = this.getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4); // Paper - optimize ifLoaded
-
- return chunk != null && !chunk.isEmpty();
- }
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 72eb8ed4f4..7e52859c1d 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -218,7 +218,7 @@ public class CraftWorld implements World {
- return false;
- }
-
-- net.minecraft.server.Chunk chunk = world.getChunkProvider().getChunkAt(x, z, false, false);
-+ net.minecraft.server.Chunk chunk = world.getChunkIfLoaded(x, z); // Paper - optimize ifLaoded
- if (chunk != null) {
- world.getChunkProvider().unload(chunk);
- }
-@@ -237,7 +237,7 @@ public class CraftWorld implements World {
-
- private boolean unloadChunk0(int x, int z, boolean save) {
- Boolean result = MCUtil.ensureMain("Unload Chunk", () -> { // Paper - Ensure never async
-- net.minecraft.server.Chunk chunk = world.getChunkProvider().getChunkAt(x, z, false, false);
-+ net.minecraft.server.Chunk chunk = world.getChunkIfLoaded(x, z); // Paper - optimize ifLoaded
- if (chunk == null) {
- return true;
- }
---
-2.21.0
-
diff --git a/patches/removed/1.14/0361-Async-Chunk-Loading-and-Generation.patch b/patches/removed/1.14/0361-Async-Chunk-Loading-and-Generation.patch
deleted file mode 100644
index 494a0dd328..0000000000
--- a/patches/removed/1.14/0361-Async-Chunk-Loading-and-Generation.patch
+++ /dev/null
@@ -1,2503 +0,0 @@
-From 3e8f931198ca4a9877415012df19ec4efc8c54b1 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 21 Jul 2018 16:55:04 -0400
-Subject: [PATCH] Async Chunk Loading and Generation
-
-This brings back parity to 1.12 and older versions in that any
-chunk requested as part of the PlayerChunkMap can be loaded
-asynchronously, since the chunk isn't needed "immediately".
-
-The previous system used by CraftBukkit has been completely abandoned, as
-mojang has put more concurrency checks into the process.
-
-The new process is no longer lock free, but tries to maintain locks as
-short as possible.
-
-But with 1.13, we now have Chunk Conversions too. A main issue about
-keeping just loading parity to 1.12 is that standard loads now
-are treated as generation level events, to run the converter on
-another thread.
-
-However mojangs code was pretty bad here and doesn't actually provide
-any concurrency...
-
-Mojangs code is still not thread safe, and can only operate on
-one world per thread safely, but this is still a major improvement
-to get world generation off of the main thread for exploration.
-
-This change brings Chunk Requests triggered by the Chunk Map to be
-lazily loaded asynchronously.
-
-Standard chunk loads can load in parallel across a shared executor.
-
-However, chunk conversions and generations must only run one per world
-at a time, so we have a single thread executor for those operations
-per world, that all of those requests get scheduled to.
-
-getChunkAt method is now thread safe, but has not been tested in
-use by other threads for generations, but should be safe to do.
-
-However, we are not encouraging plugins to go getting chunks async,
-as while looking the chunk up may be safe, absolutely nothing about
-reading or writing to the chunk will be safe, so plugins still
-should not be touching chunks asynchronously!
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-index 07d7976d21..c25db284ff 100644
---- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-@@ -376,4 +376,57 @@ public class PaperConfig {
- }
- }
- }
-+
-+ public static boolean asyncChunks = false;
-+ public static boolean asyncChunkGeneration = true;
-+ public static boolean asyncChunkGenThreadPerWorld = true;
-+ public static int asyncChunkLoadThreads = -1;
-+ private static void asyncChunks() {
-+ if (version < 15) {
-+ boolean enabled = config.getBoolean("settings.async-chunks", true);
-+ ConfigurationSection section = config.createSection("settings.async-chunks");
-+ section.set("enable", enabled);
-+ section.set("load-threads", -1);
-+ section.set("generation", true);
-+ section.set("thread-per-world-generation", true);
-+ }
-+
-+ asyncChunks = getBoolean("settings.async-chunks.enable", true);
-+ asyncChunkGeneration = getBoolean("settings.async-chunks.generation", true);
-+ asyncChunkGenThreadPerWorld = getBoolean("settings.async-chunks.thread-per-world-generation", true);
-+ asyncChunkLoadThreads = getInt("settings.async-chunks.load-threads", -1);
-+ if (asyncChunkLoadThreads <= 0) {
-+ asyncChunkLoadThreads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 8), Runtime.getRuntime().availableProcessors() * 1.5);
-+ }
-+
-+ // Let Shared Host set some limits
-+ String sharedHostEnvGen = System.getenv("PAPER_ASYNC_CHUNKS_SHARED_HOST_GEN");
-+ String sharedHostEnvLoad = System.getenv("PAPER_ASYNC_CHUNKS_SHARED_HOST_LOAD");
-+ if ("1".equals(sharedHostEnvGen)) {
-+ log("Async Chunks - Generation: Your host has requested to use a single thread world generation");
-+ asyncChunkGenThreadPerWorld = false;
-+ } else if ("2".equals(sharedHostEnvGen)) {
-+ log("Async Chunks - Generation: Your host has disabled async world generation - You will experience lag from world generation");
-+ asyncChunkGeneration = false;
-+ }
-+
-+ if (sharedHostEnvLoad != null) {
-+ try {
-+ asyncChunkLoadThreads = Math.max(1, Math.min(asyncChunkLoadThreads, Integer.parseInt(sharedHostEnvLoad)));
-+ } catch (NumberFormatException ignored) {}
-+ }
-+
-+ if (!asyncChunks) {
-+ log("Async Chunks: Disabled - Chunks will be managed synchronosuly, and will cause tremendous lag.");
-+ } else {
-+ log("Async Chunks: Enabled - Chunks will be loaded much faster, without lag.");
-+ if (!asyncChunkGeneration) {
-+ log("Async Chunks - Generation: Disabled - Chunks will be generated synchronosuly, and will cause tremendous lag.");
-+ } else if (asyncChunkGenThreadPerWorld) {
-+ log("Async Chunks - Generation: Enabled - Chunks will be generated much faster, without lag.");
-+ } else {
-+ log("Async Chunks - Generation: Enabled (Single Thread) - Chunks will be generated much faster, without lag.");
-+ }
-+ }
-+ }
- }
-diff --git a/src/main/java/com/destroystokyo/paper/util/PriorityQueuedExecutor.java b/src/main/java/com/destroystokyo/paper/util/PriorityQueuedExecutor.java
-new file mode 100644
-index 0000000000..8f18c28695
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/util/PriorityQueuedExecutor.java
-@@ -0,0 +1,347 @@
-+package com.destroystokyo.paper.util;
-+
-+import javax.annotation.Nonnull;
-+import java.util.ArrayList;
-+import java.util.List;
-+import java.util.concurrent.AbstractExecutorService;
-+import java.util.concurrent.CompletableFuture;
-+import java.util.concurrent.ConcurrentLinkedQueue;
-+import java.util.concurrent.RejectedExecutionException;
-+import java.util.concurrent.TimeUnit;
-+import java.util.concurrent.atomic.AtomicBoolean;
-+import java.util.concurrent.atomic.AtomicInteger;
-+import java.util.function.Supplier;
-+
-+/**
-+ * Implements an Executor Service that allows specifying Task Priority
-+ * and bumping of task priority.
-+ *
-+ * This is a non blocking executor with 3 priority levels.
-+ *
-+ * URGENT: Rarely used, something that is critical to take action now.
-+ * HIGH: Something with more importance than the base tasks
-+ *
-+ * @author Daniel Ennis &lt;[email protected]&gt;
-+ */
-+@SuppressWarnings({"WeakerAccess", "UnusedReturnValue", "unused"})
-+public class PriorityQueuedExecutor extends AbstractExecutorService {
-+
-+ private final ConcurrentLinkedQueue<Runnable> urgent = new ConcurrentLinkedQueue<>();
-+ private final ConcurrentLinkedQueue<Runnable> high = new ConcurrentLinkedQueue<>();
-+ private final ConcurrentLinkedQueue<Runnable> normal = new ConcurrentLinkedQueue<>();
-+ private final List<Thread> threads = new ArrayList<>();
-+ private final RejectionHandler handler;
-+
-+ private volatile boolean shuttingDown = false;
-+ private volatile boolean shuttingDownNow = false;
-+
-+ public PriorityQueuedExecutor(String name) {
-+ this(name, Math.max(1, Runtime.getRuntime().availableProcessors() - 1));
-+ }
-+
-+ public PriorityQueuedExecutor(String name, int threads) {
-+ this(name, threads, Thread.NORM_PRIORITY, null);
-+ }
-+
-+ public PriorityQueuedExecutor(String name, int threads, int threadPriority) {
-+ this(name, threads, threadPriority, null);
-+ }
-+
-+ public PriorityQueuedExecutor(String name, int threads, RejectionHandler handler) {
-+ this(name, threads, Thread.NORM_PRIORITY, handler);
-+ }
-+
-+ public PriorityQueuedExecutor(String name, int threads, int threadPriority, RejectionHandler handler) {
-+ for (int i = 0; i < threads; i++) {
-+ ExecutorThread thread = new ExecutorThread(this::processQueues);
-+ thread.setDaemon(true);
-+ thread.setName(threads == 1 ? name : name + "-" + (i + 1));
-+ thread.setPriority(threadPriority);
-+ thread.start();
-+ this.threads.add(thread);
-+ }
-+ if (handler == null) {
-+ handler = ABORT_POLICY;
-+ }
-+ this.handler = handler;
-+ }
-+
-+ /**
-+ * If the Current thread belongs to a PriorityQueuedExecutor, return that Executro
-+ * @return The executor that controls this thread
-+ */
-+ public static PriorityQueuedExecutor getExecutor() {
-+ if (!(Thread.currentThread() instanceof ExecutorThread)) {
-+ return null;
-+ }
-+ return ((ExecutorThread) Thread.currentThread()).getExecutor();
-+ }
-+
-+ public void shutdown() {
-+ shuttingDown = true;
-+ synchronized (this) {
-+ this.notifyAll();
-+ }
-+ }
-+
-+ @Nonnull
-+ @Override
-+ public List<Runnable> shutdownNow() {
-+ shuttingDown = true;
-+ shuttingDownNow = true;
-+ List<Runnable> tasks = new ArrayList<>(high.size() + normal.size());
-+ Runnable run;
-+ while ((run = getTask()) != null) {
-+ tasks.add(run);
-+ }
-+
-+ return tasks;
-+ }
-+
-+ @Override
-+ public boolean isShutdown() {
-+ return shuttingDown;
-+ }
-+
-+ @Override
-+ public boolean isTerminated() {
-+ if (!shuttingDown) {
-+ return false;
-+ }
-+ return high.isEmpty() && normal.isEmpty();
-+ }
-+
-+ @Override
-+ public boolean awaitTermination(long timeout, @Nonnull TimeUnit unit) {
-+ synchronized (this) {
-+ this.notifyAll();
-+ }
-+ final long wait = unit.toNanos(timeout);
-+ final long max = System.nanoTime() + wait;
-+ for (;!threads.isEmpty() && System.nanoTime() < max;) {
-+ threads.removeIf(thread -> !thread.isAlive());
-+ }
-+ return isTerminated();
-+ }
-+
-+
-+ public PendingTask<Void> createPendingTask(Runnable task) {
-+ return createPendingTask(task, Priority.NORMAL);
-+ }
-+ public PendingTask<Void> createPendingTask(Runnable task, Priority priority) {
-+ return createPendingTask(() -> {
-+ task.run();
-+ return null;
-+ }, priority);
-+ }
-+
-+ public <T> PendingTask<T> createPendingTask(Supplier<T> task) {
-+ return createPendingTask(task, Priority.NORMAL);
-+ }
-+
-+ public <T> PendingTask<T> createPendingTask(Supplier<T> task, Priority priority) {
-+ return new PendingTask<>(task, priority);
-+ }
-+
-+ public PendingTask<Void> submitTask(Runnable run) {
-+ return createPendingTask(run).submit();
-+ }
-+
-+ public PendingTask<Void> submitTask(Runnable run, Priority priority) {
-+ return createPendingTask(run, priority).submit();
-+ }
-+
-+ public <T> PendingTask<T> submitTask(Supplier<T> run) {
-+ return createPendingTask(run).submit();
-+ }
-+
-+ public <T> PendingTask<T> submitTask(Supplier<T> run, Priority priority) {
-+ PendingTask<T> task = createPendingTask(run, priority);
-+ return task.submit();
-+ }
-+
-+ @Override
-+ public void execute(@Nonnull Runnable command) {
-+ submitTask(command);
-+ }
-+
-+ public boolean isCurrentThread() {
-+ final Thread thread = Thread.currentThread();
-+ if (!(thread instanceof ExecutorThread)) {
-+ return false;
-+ }
-+ return ((ExecutorThread) thread).getExecutor() == this;
-+ }
-+
-+ public Runnable getUrgentTask() {
-+ return urgent.poll();
-+ }
-+
-+ public Runnable getTask() {
-+ Runnable run = urgent.poll();
-+ if (run != null) {
-+ return run;
-+ }
-+ run = high.poll();
-+ if (run != null) {
-+ return run;
-+ }
-+ return normal.poll();
-+ }
-+
-+ private void processQueues() {
-+ Runnable run = null;
-+ while (true) {
-+ if (run != null) {
-+ run.run();
-+ }
-+ if (shuttingDownNow) {
-+ return;
-+ }
-+ if ((run = getTask()) != null) {
-+ continue;
-+ }
-+ synchronized (PriorityQueuedExecutor.this) {
-+ if ((run = getTask()) != null) {
-+ continue;
-+ }
-+
-+ if (shuttingDown || shuttingDownNow) {
-+ return;
-+ }
-+ try {
-+ PriorityQueuedExecutor.this.wait();
-+ } catch (InterruptedException ignored) {
-+ }
-+ }
-+ }
-+ }
-+
-+ public boolean processUrgentTasks() {
-+ Runnable run;
-+ boolean hadTask = false;
-+ while ((run = getUrgentTask()) != null) {
-+ run.run();
-+ hadTask = true;
-+ }
-+ return hadTask;
-+ }
-+
-+ public enum Priority {
-+ NORMAL, HIGH, URGENT
-+ }
-+
-+ public class ExecutorThread extends Thread {
-+ public ExecutorThread(Runnable runnable) {
-+ super(runnable);
-+ }
-+
-+ public PriorityQueuedExecutor getExecutor() {
-+ return PriorityQueuedExecutor.this;
-+ }
-+ }
-+
-+ public class PendingTask <T> implements Runnable {
-+
-+ private final AtomicBoolean hasRan = new AtomicBoolean();
-+ private final AtomicInteger submitted = new AtomicInteger(-1);
-+ private final AtomicInteger priority;
-+ private final Supplier<T> run;
-+ private final CompletableFuture<T> future = new CompletableFuture<>();
-+ private volatile PriorityQueuedExecutor executor;
-+
-+ public PendingTask(Supplier<T> run) {
-+ this(run, Priority.NORMAL);
-+ }
-+
-+ public PendingTask(Supplier<T> run, Priority priority) {
-+ this.priority = new AtomicInteger(priority.ordinal());
-+ this.run = run;
-+ }
-+
-+ public boolean cancel() {
-+ return hasRan.compareAndSet(false, true);
-+ }
-+
-+ @Override
-+ public void run() {
-+ if (!hasRan.compareAndSet(false, true)) {
-+ return;
-+ }
-+
-+ try {
-+ future.complete(run.get());
-+ } catch (Throwable e) {
-+ future.completeExceptionally(e);
-+ }
-+ }
-+
-+ public void bumpPriority() {
-+ bumpPriority(Priority.HIGH);
-+ }
-+
-+ public void bumpPriority(Priority newPriority) {
-+ for (;;) {
-+ int current = this.priority.get();
-+ int ordinal = newPriority.ordinal();
-+ if (current >= ordinal || priority.compareAndSet(current, ordinal)) {
-+ break;
-+ }
-+ }
-+
-+
-+ if (this.submitted.get() == -1 || this.hasRan.get()) {
-+ return;
-+ }
-+
-+ // Only resubmit if it hasnt ran yet and has been submitted
-+ submit();
-+ }
-+
-+ public CompletableFuture<T> onDone() {
-+ return future;
-+ }
-+
-+ public PendingTask<T> submit() {
-+ if (shuttingDown) {
-+ handler.onRejection(this, PriorityQueuedExecutor.this);
-+ return this;
-+ }
-+ for (;;) {
-+ final int submitted = this.submitted.get();
-+ final int priority = this.priority.get();
-+ if (submitted == priority) {
-+ return this;
-+ }
-+ if (this.submitted.compareAndSet(submitted, priority)) {
-+ if (priority == Priority.URGENT.ordinal()) {
-+ urgent.add(this);
-+ } else if (priority == Priority.HIGH.ordinal()) {
-+ high.add(this);
-+ } else {
-+ normal.add(this);
-+ }
-+
-+ break;
-+ }
-+ }
-+
-+ synchronized (PriorityQueuedExecutor.this) {
-+ // Wake up a thread to take this work
-+ PriorityQueuedExecutor.this.notify();
-+ }
-+ return this;
-+ }
-+ }
-+ public interface RejectionHandler {
-+ void onRejection(Runnable run, PriorityQueuedExecutor executor);
-+ }
-+
-+ public static final RejectionHandler ABORT_POLICY = (run, executor) -> {
-+ throw new RejectedExecutionException("Executor has been shutdown");
-+ };
-+ public static final RejectionHandler CALLER_RUNS_POLICY = (run, executor) -> {
-+ run.run();
-+ };
-+
-+}
-diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
-index 4ae0acbf13..4f64072a7b 100644
---- a/src/main/java/net/minecraft/server/Chunk.java
-+++ b/src/main/java/net/minecraft/server/Chunk.java
-@@ -190,6 +190,7 @@ public class Chunk implements IChunkAccess {
-
- for (k = 0; k < this.sections.length; ++k) {
- this.sections[k] = protochunk.getSections()[k];
-+ if (this.sections[k] != null) this.sections[k].disableLocks(); // Paper - Async Chunks - disable locks used during world gen
- }
-
- Iterator iterator = protochunk.s().iterator();
-diff --git a/src/main/java/net/minecraft/server/ChunkMap.java b/src/main/java/net/minecraft/server/ChunkMap.java
-index 2021c0d02e..154ab09e0c 100644
---- a/src/main/java/net/minecraft/server/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/ChunkMap.java
-@@ -14,9 +14,17 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- }
-
- public Chunk put(long i, Chunk chunk) {
-+ org.spigotmc.AsyncCatcher.catchOp("Async Chunk put"); // Paper
- chunk.world.timings.syncChunkLoadPostTimer.startTiming(); // Paper
- lastChunkByPos = chunk; // Paper
-- Chunk chunk1 = (Chunk) super.put(i, chunk);
-+ // Paper start
-+ Chunk chunk1;
-+ synchronized (this) {
-+ // synchronize so any async gets are safe
-+ chunk1 = (Chunk) super.put(i, chunk);
-+ }
-+ if (chunk1 == null) { // Paper - we should never be overwriting chunks
-+ // Paper end
- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i);
-
- for (int j = chunkcoordintpair.x - 1; j <= chunkcoordintpair.x + 1; ++j) {
-@@ -47,7 +55,11 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- chunk.setNeighborLoaded(x, z);
- }
- }
-+ // Paper start
-+ } } else {
-+ a.error("Overwrote existing chunk! (" + chunk.world.getWorld().getName() + ":" + chunk.locX+"," + chunk.locZ + ")", new IllegalStateException());
- }
-+ // Paper end
- // Paper start - if this is a spare chunk (not part of any players view distance), go ahead and queue it for unload.
- if (!((WorldServer)chunk.world).getPlayerChunkMap().isChunkInUse(chunk.locX, chunk.locZ)) {
- if (chunk.world.paperConfig.delayChunkUnloadsBy > 0) {
-@@ -64,11 +76,19 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- }
-
- public Chunk put(Long olong, Chunk chunk) {
-- return this.put(olong, chunk);
-+ return MCUtil.ensureMain("Chunk Put", () -> this.put(olong.longValue(), chunk)); // Paper
- }
-
- public Chunk remove(long i) {
-- Chunk chunk = (Chunk) super.remove(i);
-+ // Paper start
-+ org.spigotmc.AsyncCatcher.catchOp("Async Chunk remove");
-+ Chunk chunk;
-+ synchronized (this) {
-+ // synchronize so any async gets are safe
-+ chunk = super.remove(i);
-+ }
-+ if (chunk != null) { // Paper - don't decrement if we didn't remove anything
-+ // Paper end
- ChunkCoordIntPair chunkcoordintpair = new ChunkCoordIntPair(i);
-
- for (int j = chunkcoordintpair.x - 1; j <= chunkcoordintpair.x + 1; ++j) {
-@@ -84,6 +104,7 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
- }
-
- // Paper start
-+ } // close if (chunk != null)
- if (lastChunkByPos != null && i == lastChunkByPos.chunkKey) {
- lastChunkByPos = null;
- }
-@@ -93,15 +114,22 @@ public class ChunkMap extends Long2ObjectOpenHashMap<Chunk> {
-
- @Override
- public Chunk get(long l) {
-- if (lastChunkByPos != null && l == lastChunkByPos.chunkKey) {
-- return lastChunkByPos;
-+ if (MCUtil.isMainThread()) {
-+ if (lastChunkByPos != null && l == lastChunkByPos.chunkKey) {
-+ return lastChunkByPos;
-+ }
-+ final Chunk chunk = super.get(l);
-+ return chunk != null ? (lastChunkByPos = chunk) : null;
-+ } else {
-+ synchronized (this) {
-+ return super.get(l);
-+ }
- }
-- return lastChunkByPos = super.get(l);
- }
- // Paper end
-
- public Chunk remove(Object object) {
-- return this.remove((Long) object);
-+ return MCUtil.ensureMain("Chunk Remove", () -> this.remove(((Long) object).longValue())); // Paper
- }
-
- public void putAll(Map<? extends Long, ? extends Chunk> map) {
-diff --git a/src/main/java/net/minecraft/server/ChunkProviderServer.java b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-index 186cfda7e4..781e068770 100644
---- a/src/main/java/net/minecraft/server/ChunkProviderServer.java
-+++ b/src/main/java/net/minecraft/server/ChunkProviderServer.java
-@@ -33,12 +33,12 @@ public class ChunkProviderServer implements IChunkProvider {
- private long lastProcessedSaves = 0L; // Paper
- private long lastSaveStatPrinted = System.currentTimeMillis();
- // Paper end
-- public final Long2ObjectMap<Chunk> chunks = Long2ObjectMaps.synchronize(new ChunkMap(8192));
-+ public final Long2ObjectMap<Chunk> chunks = new ChunkMap(8192); // Paper - remove synchronize - we keep everything on main for manip
- private Chunk lastChunk;
- private final ChunkTaskScheduler chunkScheduler;
-- private final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> batchScheduler;
-+ final SchedulerBatch<ChunkCoordIntPair, ChunkStatus, ProtoChunk> batchScheduler; // Paper
- public final WorldServer world;
-- private final IAsyncTaskHandler asyncTaskHandler;
-+ final IAsyncTaskHandler asyncTaskHandler; // Paper
-
- public ChunkProviderServer(WorldServer worldserver, IChunkLoader ichunkloader, ChunkGenerator<?> chunkgenerator, IAsyncTaskHandler iasynctaskhandler) {
- this.world = worldserver;
-@@ -75,10 +75,77 @@ public class ChunkProviderServer implements IChunkProvider {
- this.unloadQueue.remove(ChunkCoordIntPair.a(i, j));
- }
-
-+ // Paper start - defaults if Async Chunks is not enabled
-+ boolean chunkGoingToExists(int x, int z) {
-+ final long k = ChunkCoordIntPair.asLong(x, z);
-+ return chunkScheduler.progressCache.containsKey(k);
-+ }
-+ public void bumpPriority(ChunkCoordIntPair coords) {
-+ // do nothing, override in async
-+ }
-+
-+ public List<ChunkCoordIntPair> getSpiralOutChunks(BlockPosition blockposition, int radius) {
-+ List<ChunkCoordIntPair> list = com.google.common.collect.Lists.newArrayList();
-+
-+ list.add(new ChunkCoordIntPair(blockposition.getX() >> 4, blockposition.getZ() >> 4));
-+ for (int r = 1; r <= radius; r++) {
-+ int x = -r;
-+ int z = r;
-+
-+ // Iterates the edge of half of the box; then negates for other half.
-+ while (x <= r && z > -r) {
-+ list.add(new ChunkCoordIntPair((blockposition.getX() + (x << 4)) >> 4, (blockposition.getZ() + (z << 4)) >> 4));
-+ list.add(new ChunkCoordIntPair((blockposition.getX() - (x << 4)) >> 4, (blockposition.getZ() - (z << 4)) >> 4));
-+
-+ if (x < r) {
-+ x++;
-+ } else {
-+ z--;
-+ }
-+ }
-+ }
-+ return list;
-+ }
-+
-+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen, Consumer<Chunk> consumer) {
-+ return getChunkAt(x, z, load, gen, false, consumer);
-+ }
-+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen, boolean priority, Consumer<Chunk> consumer) {
-+ Chunk chunk = getChunkAt(x, z, load, gen);
-+ if (consumer != null) {
-+ consumer.accept(chunk);
-+ }
-+ return chunk;
-+ }
-+
-+ PaperAsyncChunkProvider.CancellableChunkRequest requestChunk(int x, int z, boolean gen, boolean priority, Consumer<Chunk> consumer) {
-+ Chunk chunk = getChunkAt(x, z, gen, priority, consumer);
-+ return new PaperAsyncChunkProvider.CancellableChunkRequest() {
-+ @Override
-+ public void cancel() {
-+
-+ }
-+
-+ @Override
-+ public Chunk getChunk() {
-+ return chunk;
-+ }
-+ };
-+ }
-+ // Paper end
-+
- @Nullable
- public Chunk getChunkAt(int i, int j, boolean flag, boolean flag1) {
- IChunkLoader ichunkloader = this.chunkLoader;
- Chunk chunk;
-+ // Paper start - do already loaded checks before synchronize
-+ long k = ChunkCoordIntPair.a(i, j);
-+ chunk = (Chunk) this.chunks.get(k);
-+ if (chunk != null) {
-+ //this.lastChunk = chunk; // Paper remove vanilla lastChunk
-+ return chunk;
-+ }
-+ // Paper end
-
- synchronized (this.chunkLoader) {
- // Paper start - remove vanilla lastChunk, we do it more accurately
-@@ -86,13 +153,15 @@ public class ChunkProviderServer implements IChunkProvider {
- return this.lastChunk;
- }*/ // Paper end
-
-- long k = ChunkCoordIntPair.a(i, j);
-+ // Paper start - move up
-+ //long k = ChunkCoordIntPair.a(i, j);
-
-- chunk = (Chunk) this.chunks.get(k);
-+ /*chunk = (Chunk) this.chunks.get(k);
- if (chunk != null) {
- //this.lastChunk = chunk; // Paper remove vanilla lastChunk
- return chunk;
-- }
-+ }*/
-+ // Paper end
-
- if (flag) {
- try (co.aikar.timings.Timing timing = world.timings.syncChunkLoadTimer.startTiming()) { // Paper
-@@ -150,7 +219,8 @@ public class ChunkProviderServer implements IChunkProvider {
- return (IChunkAccess) (chunk != null ? chunk : (IChunkAccess) this.chunkScheduler.b(new ChunkCoordIntPair(i, j), flag));
- }
-
-- public CompletableFuture<ProtoChunk> a(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) {
-+ public CompletableFuture<Void> loadAllChunks(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) { return a(iterable, consumer).thenCompose(protoChunk -> null); } // Paper - overriden in async chunk provider
-+ private CompletableFuture<ProtoChunk> a(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) { // Paper - mark private, use above method
- this.batchScheduler.b();
- Iterator iterator = iterable.iterator();
-
-@@ -168,6 +238,7 @@ public class ChunkProviderServer implements IChunkProvider {
- return this.batchScheduler.c();
- }
-
-+ ReportedException generateChunkError(int i, int j, Throwable throwable) { return a(i, j, throwable); } // Paper - OBFHELPER
- private ReportedException a(int i, int j, Throwable throwable) {
- CrashReport crashreport = CrashReport.a(throwable, "Exception generating new chunk");
- CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Chunk to be generated");
-@@ -289,11 +360,13 @@ public class ChunkProviderServer implements IChunkProvider {
- }
-
- public void close() {
-- try {
-+ // Paper start - we do not need to wait for chunk generations to finish on close
-+ /*try {
- this.batchScheduler.a();
- } catch (InterruptedException interruptedexception) {
- ChunkProviderServer.a.error("Couldn't stop taskManager", interruptedexception);
-- }
-+ }*/
-+ // Paper end
-
- }
-
-diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-index d938eb3749..7734712af9 100644
---- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
-@@ -119,7 +119,7 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- // CraftBukkit start
- private boolean check(ChunkProviderServer cps, int x, int z) throws IOException {
- if (cps != null) {
-- com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread");
-+ //com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); // Paper - this is safe
- if (cps.isLoaded(x, z)) {
- return true;
- }
-@@ -204,10 +204,13 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- }
- }
-
-+ private final Object legacyStructureLock = new Object(); // Paper
-+ public void getPersistentStructureLegacy(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection) { this.a(dimensionmanager, persistentcollection); } // Paper
- public void a(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection) {
- if (this.e == null) {
-+ synchronized (legacyStructureLock){ if (this.e == null) { // Paper
- this.e = PersistentStructureLegacy.a(dimensionmanager, persistentcollection);
-- }
-+ } } } // Paper
-
- }
-
-@@ -385,11 +388,12 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver {
- }
- };
- } else {
-+ /* // Paper start - we will never invoke this in an unsafe way
- NBTTagCompound nbttagcompound2 = this.a(world, chunkcoordintpair.x, chunkcoordintpair.z);
-
- if (nbttagcompound2 != null && this.a(nbttagcompound2) == ChunkStatus.Type.LEVELCHUNK) {
- return;
-- }
-+ }*/ // Paper end
-
- completion = new Supplier<NBTTagCompound>() {
- public NBTTagCompound get() {
-diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
-index 2af07ae592..9c6844d441 100644
---- a/src/main/java/net/minecraft/server/ChunkSection.java
-+++ b/src/main/java/net/minecraft/server/ChunkSection.java
-@@ -25,7 +25,17 @@ public class ChunkSection {
- this.skyLight = new NibbleArray();
- }
-
-+ // Paper start - Async Chunks - Lock during world gen
-+ if (chunk instanceof ProtoChunk) {
-+ this.blockIds.enableLocks();
-+ } else {
-+ this.blockIds.disableLocks();
-+ }
-+ }
-+ void disableLocks() {
-+ this.blockIds.disableLocks();
- }
-+ // Paper end
-
- public IBlockData getType(int i, int j, int k) {
- return (IBlockData) this.blockIds.a(i, j, k);
-diff --git a/src/main/java/net/minecraft/server/ChunkTaskScheduler.java b/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
-index d3898599f8..8f061f5ca3 100644
---- a/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
-+++ b/src/main/java/net/minecraft/server/ChunkTaskScheduler.java
-@@ -17,13 +17,14 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
- private final ChunkGenerator<?> d;
- private final IChunkLoader e;
- private final IAsyncTaskHandler f;
-- private final Long2ObjectMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a> progressCache = new ExpiringMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a>(8192, 5000) {
-+ protected final Long2ObjectMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a> progressCache = new ExpiringMap<Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a>(8192, 5000) { // Paper - protected
- protected boolean a(Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a scheduler_a) {
- ProtoChunk protochunk = (ProtoChunk) scheduler_a.a();
-
- return !protochunk.ab_() /*&& !protochunk.h()*/; // Paper
- }
- };
-+ private final Long2ObjectMap<java.util.concurrent.CompletableFuture<Scheduler.a>> pendingSchedulers = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>(); // Paper
-
- public ChunkTaskScheduler(int i, World world, ChunkGenerator<?> chunkgenerator, IChunkLoader ichunkloader, IAsyncTaskHandler iasynctaskhandler) {
- super("WorldGen", i, ChunkStatus.FINALIZED, () -> {
-@@ -47,8 +48,28 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
- protected Scheduler<ChunkCoordIntPair, ChunkStatus, ProtoChunk>.a a(ChunkCoordIntPair chunkcoordintpair, boolean flag) {
- IChunkLoader ichunkloader = this.e;
-
-- synchronized (this.e) {
-- return flag ? (Scheduler.a) this.progressCache.computeIfAbsent(chunkcoordintpair.a(), (i) -> {
-+ // Paper start - refactor a lot of this - avoid generating a chunk while holding lock on expiring map
-+ java.util.concurrent.CompletableFuture<Scheduler.a> pending = null;
-+ boolean created = false;
-+ long key = chunkcoordintpair.a();
-+ synchronized (pendingSchedulers) {
-+ Scheduler.a existing = this.progressCache.get(key);
-+ if (existing != null) {
-+ return existing;
-+ }
-+ pending = this.pendingSchedulers.get(key);
-+ if (pending == null) {
-+ if (!flag) {
-+ return null;
-+ }
-+ created = true;
-+ pending = new java.util.concurrent.CompletableFuture<>();
-+ pendingSchedulers.put(key, pending);
-+ }
-+ }
-+ if (created) {
-+ java.util.function.Function<Long, Scheduler.a> get = (i) -> {
-+ // Paper end
- ProtoChunk protochunk;
-
- try {
-@@ -67,8 +88,18 @@ public class ChunkTaskScheduler extends Scheduler<ChunkCoordIntPair, ChunkStatus
- } else {
- return new Scheduler.a(chunkcoordintpair, new ProtoChunk(chunkcoordintpair, ChunkConverter.a, this.getWorld()), ChunkStatus.EMPTY); // Paper - Anti-Xray
- }
-- }) : (Scheduler.a) this.progressCache.get(chunkcoordintpair.a());
-+ // Paper start
-+ };
-+ Scheduler.a scheduler = get.apply(key);
-+ progressCache.put(key, scheduler);
-+ pending.complete(scheduler);
-+ synchronized (pendingSchedulers) {
-+ pendingSchedulers.remove(key);
-+ }
-+ return scheduler;
- }
-+ return pending.join();
-+ // Paper end
- }
-
- protected ProtoChunk a(ChunkCoordIntPair chunkcoordintpair, ChunkStatus chunkstatus, Map<ChunkCoordIntPair, ProtoChunk> map) {
-diff --git a/src/main/java/net/minecraft/server/DataPaletteBlock.java b/src/main/java/net/minecraft/server/DataPaletteBlock.java
-index 454903a0e7..dcbcb655c5 100644
---- a/src/main/java/net/minecraft/server/DataPaletteBlock.java
-+++ b/src/main/java/net/minecraft/server/DataPaletteBlock.java
-@@ -3,7 +3,7 @@ package net.minecraft.server;
- import com.destroystokyo.paper.antixray.ChunkPacketInfo; // Paper - Anti-Xray
- import java.util.Arrays;
- import java.util.Objects;
--import java.util.concurrent.locks.ReentrantLock;
-+import java.util.concurrent.locks.ReentrantReadWriteLock;
- import java.util.function.Function;
- import java.util.stream.Collectors;
-
-@@ -21,26 +21,43 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
- protected DataBits a; protected DataBits getDataBits() { return this.a; } // Paper - OBFHELPER
- private DataPalette<T> h; private DataPalette<T> getDataPalette() { return this.h; } // Paper - OBFHELPER
- private int i; private int getBitsPerObject() { return this.i; } // Paper - OBFHELPER
-- private final ReentrantLock j = new ReentrantLock();
-+ // Paper start - use read write locks only during generation, disable once back on main thread
-+ private static final NoopLock NOOP_LOCK = new NoopLock();
-+ private java.util.concurrent.locks.Lock readLock = NOOP_LOCK;
-+ private java.util.concurrent.locks.Lock writeLock = NOOP_LOCK;
-+
-+ private static class NoopLock extends ReentrantReadWriteLock.WriteLock {
-+ private NoopLock() {
-+ super(new ReentrantReadWriteLock());
-+ }
-+
-+ @Override
-+ public final void lock() {
-+ }
-+
-+ @Override
-+ public final void unlock() {
-
-- private void b() {
-- if (this.j.isLocked() && !this.j.isHeldByCurrentThread()) {
-- String s = (String) Thread.getAllStackTraces().keySet().stream().filter(Objects::nonNull).map((thread) -> {
-- return thread.getName() + ": \n\tat " + (String) Arrays.stream(thread.getStackTrace()).map(Object::toString).collect(Collectors.joining("\n\tat "));
-- }).collect(Collectors.joining("\n"));
-- CrashReport crashreport = new CrashReport("Writing into PalettedContainer from multiple threads", new IllegalStateException());
-- CrashReportSystemDetails crashreportsystemdetails = crashreport.a("Thread dumps");
--
-- crashreportsystemdetails.a("Thread dumps", (Object) s);
-- throw new ReportedException(crashreport);
-- } else {
-- this.j.lock();
- }
- }
-
-+ synchronized void enableLocks() {
-+ ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
-+ readLock = lock.readLock();
-+ writeLock = lock.writeLock();
-+ }
-+ synchronized void disableLocks() {
-+ readLock = NOOP_LOCK;
-+ writeLock = NOOP_LOCK;
-+ }
-+
-+ private void b() {
-+ writeLock.lock();
-+ }
- private void c() {
-- this.j.unlock();
-+ writeLock.unlock();
- }
-+ // Paper end
-
- public DataPaletteBlock(DataPalette<T> datapalette, RegistryBlockID<T> registryblockid, Function<NBTTagCompound, T> function, Function<T, NBTTagCompound> function1, T t0) {
- // Paper start - Anti-Xray - Support default constructor
-@@ -155,9 +172,13 @@ public class DataPaletteBlock<T> implements DataPaletteExpandable<T> {
- }
-
- protected T a(int i) {
-- T t0 = this.h.a(this.a.a(i));
--
-- return t0 == null ? this.g : t0;
-+ try { // Paper start - read lock
-+ readLock.lock();
-+ T object = this.h.a(this.a.a(i)); // Paper - decompile fix
-+ return (T)(object == null ? this.g : object);
-+ } finally {
-+ readLock.unlock();
-+ } // Paper end
- }
-
- // Paper start - Anti-Xray - Support default methods
-diff --git a/src/main/java/net/minecraft/server/DefinedStructureManager.java b/src/main/java/net/minecraft/server/DefinedStructureManager.java
-index f5a6387f27..f456850997 100644
---- a/src/main/java/net/minecraft/server/DefinedStructureManager.java
-+++ b/src/main/java/net/minecraft/server/DefinedStructureManager.java
-@@ -21,7 +21,7 @@ import org.apache.logging.log4j.Logger;
- public class DefinedStructureManager implements IResourcePackListener {
-
- private static final Logger a = LogManager.getLogger();
-- private final Map<MinecraftKey, DefinedStructure> b = Maps.newHashMap();
-+ private final Map<MinecraftKey, DefinedStructure> b = Maps.newConcurrentMap(); // Paper
- private final DataFixer c;
- private final MinecraftServer d;
- private final java.nio.file.Path e;
-diff --git a/src/main/java/net/minecraft/server/Entity.java b/src/main/java/net/minecraft/server/Entity.java
-index 3d90fdb642..d6d0dd6d88 100644
---- a/src/main/java/net/minecraft/server/Entity.java
-+++ b/src/main/java/net/minecraft/server/Entity.java
-@@ -207,7 +207,7 @@ public abstract class Entity implements INamableTileEntity, ICommandListener, Ke
- this.random = SHARED_RANDOM; // Paper
- this.fireTicks = -this.getMaxFireTicks();
- this.justCreated = true;
-- this.uniqueID = MathHelper.a(this.random);
-+ this.uniqueID = MathHelper.a(java.util.concurrent.ThreadLocalRandom.current()); // Paper
- this.au = this.uniqueID.toString();
- this.aJ = Sets.newHashSet();
- this.aL = new double[] { 0.0D, 0.0D, 0.0D};
-diff --git a/src/main/java/net/minecraft/server/IChunkLoader.java b/src/main/java/net/minecraft/server/IChunkLoader.java
-index 4698ee99f8..431f4ab189 100644
---- a/src/main/java/net/minecraft/server/IChunkLoader.java
-+++ b/src/main/java/net/minecraft/server/IChunkLoader.java
-@@ -6,6 +6,9 @@ import javax.annotation.Nullable;
-
- public interface IChunkLoader {
-
-+ void getPersistentStructureLegacy(DimensionManager dimensionmanager, @Nullable PersistentCollection persistentcollection); // Paper
-+ void loadEntities(NBTTagCompound nbttagcompound, Chunk chunk); // Paper - Async Chunks
-+ Object[] loadChunk(GeneratorAccess generatoraccess, int i, int j, Consumer<Chunk> consumer) throws IOException; // Paper - Async Chunks
- @Nullable
- Chunk a(GeneratorAccess generatoraccess, int i, int j, Consumer<Chunk> consumer) throws IOException;
-
-diff --git a/src/main/java/net/minecraft/server/MathHelper.java b/src/main/java/net/minecraft/server/MathHelper.java
-index 8bb2593aa9..67bb289545 100644
---- a/src/main/java/net/minecraft/server/MathHelper.java
-+++ b/src/main/java/net/minecraft/server/MathHelper.java
-@@ -7,7 +7,7 @@ import java.util.function.IntPredicate;
- public class MathHelper {
-
- public static final float a = c(2.0F);
-- private static final float[] b = (float[]) SystemUtils.a((Object) (new float[65536]), (afloat) -> {
-+ private static final float[] b = (float[]) SystemUtils.a((new float[65536]), (afloat) -> { // Paper - Decompile fix
- for (int i = 0; i < afloat.length; ++i) {
- afloat[i] = (float) Math.sin((double) i * 3.141592653589793D * 2.0D / 65536.0D);
- }
-@@ -136,6 +136,7 @@ public class MathHelper {
- return Math.floorMod(i, j);
- }
-
-+ public static float normalizeYaw(float fx) { return g(fx); } // Paper - OBFHELPER
- public static float g(float f) {
- f %= 360.0F;
- if (f >= 180.0F) {
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index d2ee4e5781..236fbafeb5 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -499,6 +499,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
-
- // CraftBukkit start - fire WorldLoadEvent and handle whether or not to keep the spawn in memory
- Stopwatch stopwatch = Stopwatch.createStarted();
-+ boolean waitForChunks = Boolean.getBoolean("paper.waitforchunks"); // Paper
- for (WorldServer worldserver : this.getWorlds()) {
- MinecraftServer.LOGGER.info("Preparing start region for level " + worldserver.dimension + " (Seed: " + worldserver.getSeed() + ")");
- if (!worldserver.getWorld().getKeepSpawnInMemory()) {
-@@ -506,29 +507,24 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
- }
-
- BlockPosition blockposition = worldserver.getSpawn();
-- List<ChunkCoordIntPair> list = Lists.newArrayList();
-+ List<ChunkCoordIntPair> list = worldserver.getChunkProvider().getSpiralOutChunks(blockposition, worldserver.paperConfig.keepLoadedRange >> 4); // Paper
- Set<ChunkCoordIntPair> set = Sets.newConcurrentHashSet();
-
-- // Paper start
-- short radius = worldserver.paperConfig.keepLoadedRange;
-- for (int i = -radius; i <= radius && this.isRunning(); i += 16) {
-- for (int j = -radius; j <= radius && this.isRunning(); j += 16) {
-- // Paper end
-- list.add(new ChunkCoordIntPair(blockposition.getX() + i >> 4, blockposition.getZ() + j >> 4));
-- }
-- } // Paper
-+ // Paper - remove arraylist creation, call spiral above
- if (this.isRunning()) { // Paper
- int expected = list.size(); // Paper
-
--
-- CompletableFuture completablefuture = worldserver.getChunkProvider().a((Iterable) list, (chunk) -> {
-+ CompletableFuture completablefuture = worldserver.getChunkProvider().loadAllChunks(list, (chunk) -> { // Paper
- set.add(chunk.getPos());
-- if (set.size() < expected && set.size() % 25 == 0) this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
-+ if (waitForChunks && (set.size() == expected || (set.size() < expected && set.size() % (set.size() / 10) == 0))) {
-+ this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
-+ }
- });
-
-- while (!completablefuture.isDone()) {
-+ while (waitForChunks && !completablefuture.isDone() && isRunning()) { // Paper
- try {
-- completablefuture.get(1L, TimeUnit.SECONDS);
-+ PaperAsyncChunkProvider.processMainThreadQueue(this); // Paper
-+ completablefuture.get(50L, TimeUnit.MILLISECONDS); // Paper
- } catch (InterruptedException interruptedexception) {
- throw new RuntimeException(interruptedexception);
- } catch (ExecutionException executionexception) {
-@@ -538,11 +534,11 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
-
- throw new RuntimeException(executionexception.getCause());
- } catch (TimeoutException timeoutexception) {
-- this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
-+ //this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
- }
- }
-
-- this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
-+ if (waitForChunks) this.a(new ChatMessage("menu.preparingSpawn", new Object[0]), set.size() * 100 / expected); // Paper
- }
- }
-
-@@ -646,6 +642,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
- if (hasStopped) return;
- hasStopped = true;
- }
-+ PaperAsyncChunkProvider.stop(this); // Paper
- // CraftBukkit end
- MinecraftServer.LOGGER.info("Stopping server");
- MinecraftTimings.stopServer(); // Paper
-@@ -1013,6 +1010,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
- while ((futuretask = (FutureTask) this.f.poll()) != null) {
- SystemUtils.a(futuretask, MinecraftServer.LOGGER);
- }
-+ PaperAsyncChunkProvider.processMainThreadQueue(this); // Paper
- MinecraftTimings.minecraftSchedulerTimer.stopTiming(); // Paper
-
- this.methodProfiler.exitEnter("commandFunctions");
-@@ -1049,6 +1047,7 @@ public abstract class MinecraftServer implements IAsyncTaskHandler, IMojangStati
- // CraftBukkit - dropTickTime
- for (Iterator iterator = this.getWorlds().iterator(); iterator.hasNext();) {
- WorldServer worldserver = (WorldServer) iterator.next();
-+ PaperAsyncChunkProvider.processMainThreadQueue(worldserver); // Paper
- worldserver.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper
- TileEntityHopper.skipHopperEvents = worldserver.paperConfig.disableHopperMoveEvents || org.bukkit.event.inventory.InventoryMoveItemEvent.getHandlerList().getRegisteredListeners().length == 0; // Paper
- i = SystemUtils.getMonotonicNanos();
-diff --git a/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java b/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java
-new file mode 100644
-index 0000000000..851756d65e
---- /dev/null
-+++ b/src/main/java/net/minecraft/server/PaperAsyncChunkProvider.java
-@@ -0,0 +1,658 @@
-+/*
-+ * This file is licensed under the MIT License (MIT).
-+ *
-+ * Copyright (c) 2018 Daniel Ennis <http://aikar.co>
-+ *
-+ * Permission is hereby granted, free of charge, to any person obtaining a copy
-+ * of this software and associated documentation files (the "Software"), to deal
-+ * in the Software without restriction, including without limitation the rights
-+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-+ * copies of the Software, and to permit persons to whom the Software is
-+ * furnished to do so, subject to the following conditions:
-+ *
-+ * The above copyright notice and this permission notice shall be included in
-+ * all copies or substantial portions of the Software.
-+ *
-+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-+ * THE SOFTWARE.
-+ */
-+package net.minecraft.server;
-+
-+import com.destroystokyo.paper.PaperConfig;
-+import com.destroystokyo.paper.util.PriorityQueuedExecutor;
-+import com.destroystokyo.paper.util.PriorityQueuedExecutor.Priority;
-+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
-+import it.unimi.dsi.fastutil.longs.Long2ObjectMaps;
-+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.generator.CustomChunkGenerator;
-+import org.bukkit.craftbukkit.generator.InternalChunkGenerator;
-+
-+import javax.annotation.Nullable;
-+import java.io.IOException;
-+import java.util.ArrayList;
-+import java.util.Iterator;
-+import java.util.List;
-+import java.util.concurrent.CompletableFuture;
-+import java.util.concurrent.ConcurrentLinkedDeque;
-+import java.util.concurrent.atomic.AtomicBoolean;
-+import java.util.concurrent.atomic.AtomicInteger;
-+import java.util.function.Consumer;
-+
-+@SuppressWarnings("unused")
-+public class PaperAsyncChunkProvider extends ChunkProviderServer {
-+
-+ private static final int GEN_THREAD_PRIORITY = Integer.getInteger("paper.genThreadPriority", 3);
-+ private static final int LOAD_THREAD_PRIORITY = Integer.getInteger("paper.loadThreadPriority", 4);
-+ private static final PriorityQueuedExecutor EXECUTOR = new PriorityQueuedExecutor("PaperChunkLoader", PaperConfig.asyncChunks ? PaperConfig.asyncChunkLoadThreads : 0, LOAD_THREAD_PRIORITY);
-+ private static final PriorityQueuedExecutor SINGLE_GEN_EXECUTOR = new PriorityQueuedExecutor("PaperChunkGenerator", PaperConfig.asyncChunks && PaperConfig.asyncChunkGeneration && !PaperConfig.asyncChunkGenThreadPerWorld ? 1 : 0, GEN_THREAD_PRIORITY);
-+ private static final ConcurrentLinkedDeque<Runnable> MAIN_THREAD_QUEUE = new ConcurrentLinkedDeque<>();
-+
-+ private final PriorityQueuedExecutor generationExecutor;
-+ //private static final PriorityQueuedExecutor generationExecutor = new PriorityQueuedExecutor("PaperChunkGen", 1);
-+ private final Long2ObjectMap<PendingChunk> pendingChunks = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>());
-+ private final IAsyncTaskHandler asyncHandler;
-+
-+ private final WorldServer world;
-+ private final IChunkLoader chunkLoader;
-+ private final MinecraftServer server;
-+ private final boolean shouldGenSync;
-+
-+ public PaperAsyncChunkProvider(WorldServer world, IChunkLoader chunkLoader, InternalChunkGenerator generator, MinecraftServer server) {
-+ super(world, chunkLoader, generator, server);
-+
-+ this.server = world.getMinecraftServer();
-+ this.world = world;
-+ this.asyncHandler = server;
-+ this.chunkLoader = chunkLoader;
-+ String worldName = this.world.getWorld().getName();
-+ this.shouldGenSync = generator instanceof CustomChunkGenerator && !(((CustomChunkGenerator) generator).asyncSupported) || !PaperConfig.asyncChunkGeneration;
-+ this.generationExecutor = PaperConfig.asyncChunkGenThreadPerWorld ? new PriorityQueuedExecutor("PaperChunkGen-" + worldName, shouldGenSync ? 0 : 1, GEN_THREAD_PRIORITY) : SINGLE_GEN_EXECUTOR;
-+ }
-+
-+ private static Priority calculatePriority(boolean isBlockingMain, boolean priority) {
-+ if (isBlockingMain) {
-+ return Priority.URGENT;
-+ }
-+
-+ if (priority) {
-+ return Priority.HIGH;
-+ }
-+
-+ return Priority.NORMAL;
-+ }
-+
-+ static void stop(MinecraftServer server) {
-+ for (WorldServer world : server.getWorlds()) {
-+ world.getPlayerChunkMap().shutdown();
-+ }
-+ }
-+
-+ static void processMainThreadQueue(MinecraftServer server) {
-+ for (WorldServer world : server.getWorlds()) {
-+ processMainThreadQueue(world);
-+ }
-+ }
-+
-+ static void processMainThreadQueue(World world) {
-+ IChunkProvider chunkProvider = world.getChunkProvider();
-+ if (chunkProvider instanceof PaperAsyncChunkProvider) {
-+ ((PaperAsyncChunkProvider) chunkProvider).processMainThreadQueue();
-+ }
-+ }
-+
-+ private void processMainThreadQueue() {
-+ processMainThreadQueue((PendingChunk) null);
-+ }
-+ private boolean processMainThreadQueue(PendingChunk pending) {
-+ Runnable run;
-+ boolean hadLoad = false;
-+ while ((run = MAIN_THREAD_QUEUE.poll()) != null) {
-+ run.run();
-+ hadLoad = true;
-+ if (pending != null && pending.hasPosted) {
-+ break;
-+ }
-+ }
-+ return hadLoad;
-+ }
-+
-+ @Override
-+ public void bumpPriority(ChunkCoordIntPair coords) {
-+ final PendingChunk pending = pendingChunks.get(coords.asLong());
-+ if (pending != null) {
-+ pending.bumpPriority(Priority.HIGH);
-+ }
-+ }
-+
-+ @Nullable
-+ @Override
-+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen) {
-+ return getChunkAt(x, z, load, gen, null);
-+ }
-+
-+ @Nullable
-+ @Override
-+ public Chunk getChunkAt(int x, int z, boolean load, boolean gen, boolean priority, Consumer<Chunk> consumer) {
-+ final long key = ChunkCoordIntPair.asLong(x, z);
-+ final Chunk chunk = this.chunks.get(key);
-+ if (chunk != null || !load) { // return null if we aren't loading
-+ if (consumer != null) {
-+ consumer.accept(chunk);
-+ }
-+ return chunk;
-+ }
-+ return loadOrGenerateChunk(x, z, gen, priority, consumer); // Async overrides this method
-+ }
-+
-+ private Chunk loadOrGenerateChunk(int x, int z, boolean gen, boolean priority, Consumer<Chunk> consumer) {
-+ return requestChunk(x, z, gen, priority, consumer).getChunk();
-+ }
-+
-+ final PendingChunkRequest requestChunk(int x, int z, boolean gen, boolean priority, Consumer<Chunk> consumer) {
-+ try (co.aikar.timings.Timing timing = world.timings.syncChunkLoadTimer.startTiming()) {
-+ final long key = ChunkCoordIntPair.asLong(x, z);
-+ final boolean isChunkThread = isChunkThread();
-+ final boolean isBlockingMain = consumer == null && server.isMainThread();
-+ final boolean loadOnThisThread = isChunkThread || isBlockingMain;
-+ final Priority taskPriority = calculatePriority(isBlockingMain, priority);
-+
-+ // Obtain a PendingChunk
-+ final PendingChunk pending;
-+ synchronized (pendingChunks) {
-+ PendingChunk pendingChunk = pendingChunks.get(key);
-+ if (pendingChunk == null) {
-+ pending = new PendingChunk(x, z, key, gen, taskPriority);
-+ pendingChunks.put(key, pending);
-+ } else if (pendingChunk.hasFinished && gen && !pendingChunk.canGenerate && pendingChunk.chunk == null) {
-+ // need to overwrite the old
-+ pending = new PendingChunk(x, z, key, true, taskPriority);
-+ pendingChunks.put(key, pending);
-+ } else {
-+ pending = pendingChunk;
-+ if (pending.taskPriority != taskPriority) {
-+ pending.bumpPriority(taskPriority);
-+ }
-+ }
-+ }
-+
-+ // Listen for when result is ready
-+ final CompletableFuture<Chunk> future = new CompletableFuture<>();
-+ final PendingChunkRequest request = pending.addListener(future, gen, !loadOnThisThread);
-+
-+ // Chunk Generation can trigger Chunk Loading, those loads may need to convert, and could be slow
-+ // Give an opportunity for urgent tasks to jump in at these times
-+ if (isChunkThread) {
-+ processUrgentTasks();
-+ }
-+
-+ if (loadOnThisThread) {
-+ // do loads on main if blocking, or on current if we are a load/gen thread
-+ // gen threads do trigger chunk loads
-+ pending.loadTask.run();
-+ }
-+
-+ if (isBlockingMain) {
-+ while (!future.isDone()) {
-+ // We aren't done, obtain lock on queue
-+ synchronized (MAIN_THREAD_QUEUE) {
-+ // We may of received our request now, check it
-+ if (processMainThreadQueue(pending)) {
-+ // If we processed SOMETHING, don't wait
-+ continue;
-+ }
-+ try {
-+ // We got nothing from the queue, wait until something has been added
-+ MAIN_THREAD_QUEUE.wait(1);
-+ } catch (InterruptedException ignored) {
-+ }
-+ }
-+ // Queue has been notified or timed out, process it
-+ processMainThreadQueue(pending);
-+ }
-+ // We should be done AND posted into chunk map now, return it
-+ request.initialReturnChunk = pending.postChunk();
-+ } else if (consumer == null) {
-+ // This is on another thread
-+ request.initialReturnChunk = future.join();
-+ } else {
-+ future.thenAccept((c) -> this.asyncHandler.postToMainThread(() -> consumer.accept(c)));
-+ }
-+
-+ return request;
-+ }
-+ }
-+
-+ private void processUrgentTasks() {
-+ final PriorityQueuedExecutor executor = PriorityQueuedExecutor.getExecutor();
-+ if (executor != null) {
-+ executor.processUrgentTasks();
-+ }
-+ }
-+
-+ @Override
-+ public CompletableFuture<Void> loadAllChunks(Iterable<ChunkCoordIntPair> iterable, Consumer<Chunk> consumer) {
-+ final Iterator<ChunkCoordIntPair> iterator = iterable.iterator();
-+
-+ final List<CompletableFuture<Chunk>> all = new ArrayList<>();
-+ while (iterator.hasNext()) {
-+ final ChunkCoordIntPair chunkcoordintpair = iterator.next();
-+ final CompletableFuture<Chunk> future = new CompletableFuture<>();
-+ all.add(future);
-+ this.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, true, true, chunk -> {
-+ future.complete(chunk);
-+ if (consumer != null) {
-+ consumer.accept(chunk);
-+ }
-+ });
-+ }
-+ return CompletableFuture.allOf(all.toArray(new CompletableFuture[0]));
-+ }
-+
-+ boolean chunkGoingToExists(int x, int z) {
-+ synchronized (pendingChunks) {
-+ PendingChunk pendingChunk = pendingChunks.get(ChunkCoordIntPair.asLong(x, z));
-+ return pendingChunk != null && pendingChunk.canGenerate;
-+ }
-+ }
-+
-+ private enum PendingStatus {
-+ /**
-+ * Request has just started
-+ */
-+ STARTED,
-+ /**
-+ * Chunk is attempting to be loaded from disk
-+ */
-+ LOADING,
-+ /**
-+ * Chunk must generate on main and is pending main
-+ */
-+ GENERATION_PENDING,
-+ /**
-+ * Chunk is generating
-+ */
-+ GENERATING,
-+ /**
-+ * Chunk is ready and is pending post to main
-+ */
-+ PENDING_MAIN,
-+ /**
-+ * Could not load chunk, and did not need to generat
-+ */
-+ FAIL,
-+ /**
-+ * Fully done with this request (may or may not of loaded)
-+ */
-+ DONE,
-+ /**
-+ * Chunk load was cancelled (no longer needed)
-+ */
-+ CANCELLED
-+ }
-+
-+ public interface CancellableChunkRequest {
-+ void cancel();
-+ Chunk getChunk();
-+ }
-+
-+ public static class PendingChunkRequest implements CancellableChunkRequest {
-+ private final PendingChunk pending;
-+ private final AtomicBoolean cancelled = new AtomicBoolean(false);
-+ private volatile boolean generating;
-+ private volatile Chunk initialReturnChunk;
-+
-+ private PendingChunkRequest(PendingChunk pending) {
-+ this.pending = pending;
-+ this.cancelled.set(true);
-+ }
-+
-+ private PendingChunkRequest(PendingChunk pending, boolean gen) {
-+ this.pending = pending;
-+ this.generating = gen;
-+ }
-+
-+ public void cancel() {
-+ this.pending.cancel(this);
-+ }
-+
-+ /**
-+ * Will be null on asynchronous loads
-+ */
-+ @Override @Nullable
-+ public Chunk getChunk() {
-+ return initialReturnChunk;
-+ }
-+ }
-+
-+ private boolean isLoadThread() {
-+ return EXECUTOR.isCurrentThread();
-+ }
-+
-+ private boolean isGenThread() {
-+ return generationExecutor.isCurrentThread();
-+ }
-+ private boolean isChunkThread() {
-+ return isLoadThread() || isGenThread();
-+ }
-+
-+ private class PendingChunk implements Runnable {
-+ private final int x;
-+ private final int z;
-+ private final long key;
-+ private final long started = System.currentTimeMillis();
-+ private final CompletableFuture<Chunk> loadOnly = new CompletableFuture<>();
-+ private final CompletableFuture<Chunk> generate = new CompletableFuture<>();
-+ private final AtomicInteger requests = new AtomicInteger(0);
-+
-+ private volatile PendingStatus status = PendingStatus.STARTED;
-+ private volatile PriorityQueuedExecutor.PendingTask<Void> loadTask;
-+ private volatile PriorityQueuedExecutor.PendingTask<Chunk> genTask;
-+ private volatile Priority taskPriority;
-+ private volatile boolean generating;
-+ private volatile boolean canGenerate;
-+ private volatile boolean isHighPriority;
-+ private volatile boolean hasPosted;
-+ private volatile boolean hasFinished;
-+ private volatile Chunk chunk;
-+ private volatile NBTTagCompound pendingLevel;
-+
-+ PendingChunk(int x, int z, long key, boolean canGenerate, boolean priority) {
-+ this.x = x;
-+ this.z = z;
-+ this.key = key;
-+ this.canGenerate = canGenerate;
-+ taskPriority = priority ? Priority.HIGH : Priority.NORMAL;
-+ }
-+
-+ PendingChunk(int x, int z, long key, boolean canGenerate, Priority taskPriority) {
-+ this.x = x;
-+ this.z = z;
-+ this.key = key;
-+ this.canGenerate = canGenerate;
-+ this.taskPriority = taskPriority;
-+ }
-+
-+ private synchronized void setStatus(PendingStatus status) {
-+ this.status = status;
-+ }
-+
-+ private Chunk loadChunk(int x, int z) throws IOException {
-+ setStatus(PendingStatus.LOADING);
-+ Object[] data = chunkLoader.loadChunk(world, x, z, null);
-+ if (data != null) {
-+ // Level must be loaded on main
-+ this.pendingLevel = ((NBTTagCompound) data[1]).getCompound("Level");
-+ return (Chunk) data[0];
-+ } else {
-+ return null;
-+ }
-+ }
-+
-+ private Chunk generateChunk() {
-+ synchronized (this) {
-+ if (requests.get() <= 0) {
-+ return null;
-+ }
-+ }
-+
-+ try {
-+ CompletableFuture<Chunk> pending = new CompletableFuture<>();
-+ batchScheduler.startBatch();
-+ batchScheduler.add(new ChunkCoordIntPair(x, z));
-+
-+ ProtoChunk protoChunk = batchScheduler.executeBatch().join();
-+ boolean saved = false;
-+ if (!Bukkit.isPrimaryThread()) {
-+ // If we are async, dispatch later
-+ try {
-+ chunkLoader.saveChunk(world, protoChunk, true);
-+ saved = true;
-+ } catch (IOException | ExceptionWorldConflict e) {
-+ e.printStackTrace();
-+ }
-+ }
-+ Chunk chunk = new Chunk(world, protoChunk, x, z);
-+ if (saved) {
-+ chunk.setLastSaved(world.getTime());
-+ }
-+ generateFinished(chunk);
-+
-+ return chunk;
-+ } catch (Throwable e) {
-+ MinecraftServer.LOGGER.error("Couldn't generate chunk (" +world.getWorld().getName() + ":" + x + "," + z + ")", e);
-+ generateFinished(null);
-+ return null;
-+ }
-+ }
-+
-+ boolean loadFinished(Chunk chunk) {
-+ if (chunk != null) {
-+ postChunkToMain(chunk);
-+ return false;
-+ }
-+ loadOnly.complete(null);
-+
-+ synchronized (this) {
-+ boolean cancelled = requests.get() <= 0;
-+ if (!canGenerate || cancelled) {
-+ if (!cancelled) {
-+ setStatus(PendingStatus.FAIL);
-+ }
-+ this.chunk = null;
-+ this.hasFinished = true;
-+ pendingChunks.remove(key);
-+ return false;
-+ } else {
-+ setStatus(PendingStatus.GENERATING);
-+ generating = true;
-+ return true;
-+ }
-+ }
-+ }
-+
-+ void generateFinished(Chunk chunk) {
-+ synchronized (this) {
-+ this.chunk = chunk;
-+ this.hasFinished = true;
-+ }
-+ if (chunk != null) {
-+ postChunkToMain(chunk);
-+ } else {
-+ synchronized (this) {
-+ pendingChunks.remove(key);
-+ completeFutures(null);
-+ }
-+ }
-+ }
-+
-+ synchronized private void completeFutures(Chunk chunk) {
-+ loadOnly.complete(chunk);
-+ generate.complete(chunk);
-+ }
-+
-+ private void postChunkToMain(Chunk chunk) {
-+ synchronized (this) {
-+ setStatus(PendingStatus.PENDING_MAIN);
-+ this.chunk = chunk;
-+ this.hasFinished = true;
-+ }
-+
-+ if (server.isMainThread()) {
-+ postChunk();
-+ return;
-+ }
-+
-+ // Don't post here, even if on main, it must enter the queue so we can exit any open batch
-+ // schedulers, as post stage may trigger a new generation and cause errors
-+ synchronized (MAIN_THREAD_QUEUE) {
-+ if (this.taskPriority == Priority.URGENT) {
-+ MAIN_THREAD_QUEUE.addFirst(this::postChunk);
-+ } else {
-+ MAIN_THREAD_QUEUE.addLast(this::postChunk);
-+ }
-+ MAIN_THREAD_QUEUE.notify();
-+ }
-+ }
-+
-+ Chunk postChunk() {
-+ if (!server.isMainThread()) {
-+ throw new IllegalStateException("Must post from main");
-+ }
-+ synchronized (this) {
-+ if (hasPosted || requests.get() <= 0) { // if pending is 0, all were cancelled
-+ return chunk;
-+ }
-+ hasPosted = true;
-+ }
-+ try {
-+ if (chunk == null) {
-+ chunk = chunks.get(key);
-+ completeFutures(chunk);
-+ return chunk;
-+ }
-+ if (pendingLevel != null) {
-+ chunkLoader.loadEntities(pendingLevel, chunk);
-+ pendingLevel = null;
-+ }
-+ synchronized (chunks) {
-+ final Chunk other = chunks.get(key);
-+ if (other != null) {
-+ this.chunk = other;
-+ completeFutures(other);
-+ return other;
-+ }
-+ if (chunk != null) {
-+ chunks.put(key, chunk);
-+ }
-+ }
-+
-+ chunk.addEntities();
-+
-+ completeFutures(chunk);
-+ return chunk;
-+ } finally {
-+ pendingChunks.remove(key);
-+ setStatus(PendingStatus.DONE);
-+ }
-+ }
-+
-+ synchronized PendingChunkRequest addListener(CompletableFuture<Chunk> future, boolean gen, boolean autoSubmit) {
-+ requests.incrementAndGet();
-+ if (loadTask == null) {
-+ // Take care of a race condition in that a request could be cancelled after the synchronize
-+ // on pendingChunks, but before a listener is added, which would erase these pending tasks.
-+ genTask = generationExecutor.createPendingTask(this::generateChunk, taskPriority);
-+ loadTask = EXECUTOR.createPendingTask(this, taskPriority);
-+ if (autoSubmit) {
-+ // We will execute it outside of the synchronized context immediately after
-+ loadTask.submit();
-+ }
-+ }
-+
-+ if (hasFinished) {
-+ future.complete(chunk);
-+ return new PendingChunkRequest(this);
-+ } else if (gen) {
-+ canGenerate = true;
-+ generate.thenAccept(future::complete);
-+ } else {
-+ if (generating) {
-+ future.complete(null);
-+ return new PendingChunkRequest(this);
-+ } else {
-+ loadOnly.thenAccept(future::complete);
-+ }
-+ }
-+
-+ return new PendingChunkRequest(this, gen);
-+ }
-+
-+ @Override
-+ public void run() {
-+ try {
-+ if (!loadFinished(loadChunk(x, z))) {
-+ return;
-+ }
-+ } catch (Throwable ex) {
-+ MinecraftServer.LOGGER.error("Couldn't load chunk (" +world.getWorld().getName() + ":" + x + "," + z + ")", ex);
-+ if (ex instanceof IOException) {
-+ generateFinished(null);
-+ return;
-+ }
-+ }
-+
-+ if (shouldGenSync) {
-+ synchronized (this) {
-+ setStatus(PendingStatus.GENERATION_PENDING);
-+ if (this.taskPriority == Priority.URGENT) {
-+ MAIN_THREAD_QUEUE.addFirst(() -> generateFinished(this.generateChunk()));
-+ } else {
-+ MAIN_THREAD_QUEUE.addLast(() -> generateFinished(this.generateChunk()));
-+ }
-+
-+ }
-+ synchronized (MAIN_THREAD_QUEUE) {
-+ MAIN_THREAD_QUEUE.notify();
-+ }
-+ } else {
-+ if (isGenThread()) {
-+ // ideally we should never run into 1 chunk generating another chunk...
-+ // but if we do, let's apply same solution
-+ genTask.run();
-+ } else {
-+ genTask.submit();
-+ }
-+ }
-+ }
-+
-+ void bumpPriority() {
-+ bumpPriority(Priority.HIGH);
-+ }
-+
-+ void bumpPriority(Priority newPriority) {
-+ if (taskPriority.ordinal() >= newPriority.ordinal()) {
-+ return;
-+ }
-+
-+ this.taskPriority = newPriority;
-+ PriorityQueuedExecutor.PendingTask<Void> loadTask = this.loadTask;
-+ PriorityQueuedExecutor.PendingTask<Chunk> genTask = this.genTask;
-+ if (loadTask != null) {
-+ loadTask.bumpPriority(newPriority);
-+ }
-+ if (genTask != null) {
-+ genTask.bumpPriority(newPriority);
-+ }
-+ }
-+
-+ public synchronized boolean isCancelled() {
-+ return requests.get() <= 0;
-+ }
-+
-+ public synchronized void cancel(PendingChunkRequest request) {
-+ synchronized (pendingChunks) {
-+ if (!request.cancelled.compareAndSet(false, true)) {
-+ return;
-+ }
-+
-+ if (requests.decrementAndGet() > 0) {
-+ return;
-+ }
-+
-+ boolean c1 = genTask.cancel();
-+ boolean c2 = loadTask.cancel();
-+ loadTask = null;
-+ genTask = null;
-+ pendingChunks.remove(key);
-+ setStatus(PendingStatus.CANCELLED);
-+ }
-+ }
-+ }
-+
-+}
-diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
-index 240f590666..e4cf8548d3 100644
---- a/src/main/java/net/minecraft/server/PlayerChunk.java
-+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
-@@ -22,6 +22,52 @@ public class PlayerChunk {
- private long i;
- private boolean done;
- boolean chunkExists; // Paper
-+ // Paper start
-+ PaperAsyncChunkProvider.CancellableChunkRequest chunkRequest;
-+ private java.util.function.Consumer<Chunk> chunkLoadedConsumer = chunk -> {
-+ chunkRequest = null;
-+ PlayerChunk pChunk = PlayerChunk.this;
-+ pChunk.chunk = chunk;
-+ markChunkUsed(); // Paper - delay chunk unloads
-+ };
-+ private boolean markedHigh = false;
-+ void checkHighPriority(EntityPlayer player) {
-+ if (done || markedHigh || chunk != null) {
-+ return;
-+ }
-+ final double dist = getDistance(player.locX, player.locZ);
-+ if (dist > 8) {
-+ return;
-+ }
-+ if (dist >= 3 && getDistance(player, 5) > 3.5) {
-+ return;
-+ }
-+
-+ markedHigh = true;
-+ playerChunkMap.getWorld().getChunkProvider().bumpPriority(location);
-+ if (chunkRequest == null) {
-+ requestChunkIfNeeded(PlayerChunkMap.CAN_GEN_CHUNKS.test(player));
-+ }
-+ }
-+ private void requestChunkIfNeeded(boolean flag) {
-+ if (chunkRequest == null) {
-+ chunkRequest = this.playerChunkMap.getWorld().getChunkProvider().requestChunk(this.location.x, this.location.z, flag, markedHigh, chunkLoadedConsumer);
-+ this.chunk = chunkRequest.getChunk(); // Paper)
-+ markChunkUsed(); // Paper - delay chunk unloads
-+ }
-+ }
-+ private double getDistance(EntityPlayer player, int inFront) {
-+ final float yaw = MathHelper.normalizeYaw(player.yaw);
-+ final double x = player.locX + (inFront * Math.cos(Math.toRadians(yaw)));
-+ final double z = player.locZ + (inFront * Math.sin(Math.toRadians(yaw)));
-+ return getDistance(x, z);
-+ }
-+
-+ private double getDistance(double blockX, double blockZ) {
-+ final double x = location.x - ((int)Math.floor(blockX) >> 4);
-+ final double z = location.z - ((int)Math.floor(blockZ) >> 4);
-+ return Math.sqrt((x * x) + (z * z));
-+ }
-
- public PlayerChunk(PlayerChunkMap playerchunkmap, int i, int j) {
- this.playerChunkMap = playerchunkmap;
-@@ -29,13 +75,17 @@ public class PlayerChunk {
- ChunkProviderServer chunkproviderserver = playerchunkmap.getWorld().getChunkProvider();
-
- chunkproviderserver.a(i, j);
-- this.chunk = chunkproviderserver.getChunkAt(i, j, true, false);
-- this.chunkExists = this.chunk != null || org.bukkit.craftbukkit.chunkio.ChunkIOExecutor.hasQueuedChunkLoad(playerChunkMap.getWorld(), i, j); // Paper
-+ this.chunk = chunkproviderserver.getChunkAt(i, j, false, false); // Paper
-+ this.chunkExists = this.chunk != null || chunkproviderserver.chunkGoingToExists(i, j); // Paper
- markChunkUsed(); // Paper - delay chunk unloads
- }
-
- // Paper start
- private void markChunkUsed() {
-+ if (!chunkHasPlayers && chunkRequest != null) {
-+ chunkRequest.cancel();
-+ chunkRequest = null;
-+ }
- if (chunk == null) {
- return;
- }
-@@ -65,7 +115,7 @@ public class PlayerChunk {
- this.players.add(entityplayer);
- if (this.done) {
- this.sendChunk(entityplayer);
-- }
-+ } else checkHighPriority(entityplayer); // Paper
-
- }
- }
-@@ -90,8 +140,9 @@ public class PlayerChunk {
- if (this.chunk != null) {
- return true;
- } else {
-- this.chunk = this.playerChunkMap.getWorld().getChunkProvider().getChunkAt(this.location.x, this.location.z, true, flag);
-- markChunkUsed(); // Paper - delay chunk unloads
-+ // Paper start - async chunks
-+ requestChunkIfNeeded(flag);
-+ // Paper end
- return this.chunk != null;
- }
- }
-diff --git a/src/main/java/net/minecraft/server/PlayerChunkMap.java b/src/main/java/net/minecraft/server/PlayerChunkMap.java
-index 679488a3cf..b7dda8e282 100644
---- a/src/main/java/net/minecraft/server/PlayerChunkMap.java
-+++ b/src/main/java/net/minecraft/server/PlayerChunkMap.java
-@@ -25,10 +25,10 @@ public class PlayerChunkMap {
- };
- private static final Predicate<EntityPlayer> b = (entityplayer) -> {
- return entityplayer != null && (!entityplayer.isSpectator() || entityplayer.getWorldServer().getGameRules().getBoolean("spectatorsGenerateChunks"));
-- };
-+ }; static final Predicate<EntityPlayer> CAN_GEN_CHUNKS = b; // Paper - OBFHELPER
- private final WorldServer world;
- private final List<EntityPlayer> managedPlayers = Lists.newArrayList();
-- private final Long2ObjectMap<PlayerChunk> e = new Long2ObjectOpenHashMap(4096);
-+ private final Long2ObjectMap<PlayerChunk> e = new Long2ObjectOpenHashMap(4096); Long2ObjectMap<PlayerChunk> getChunks() { return e; } // Paper - OBFHELPER
- private final Set<PlayerChunk> f = Sets.newHashSet();
- private final List<PlayerChunk> g = Lists.newLinkedList();
- private final List<PlayerChunk> h = Lists.newLinkedList();
-@@ -341,7 +341,13 @@ public class PlayerChunkMap {
- if (playerchunk != null) {
- playerchunk.b(entityplayer);
- }
-+ } else { // Paper start
-+ PlayerChunk playerchunk = this.getChunk(l1 - j1, i2 - k1);
-+ if (playerchunk != null) {
-+ playerchunk.checkHighPriority(entityplayer); // Paper
-+ }
- }
-+ // Paper end
- }
- }
-
-@@ -352,7 +358,11 @@ public class PlayerChunkMap {
- // CraftBukkit start - send nearest chunks first
- Collections.sort(chunksToLoad, new ChunkCoordComparator(entityplayer));
- for (ChunkCoordIntPair pair : chunksToLoad) {
-- this.c(pair.x, pair.z).a(entityplayer);
-+ // Paper start
-+ PlayerChunk c = this.c(pair.x, pair.z);
-+ c.checkHighPriority(entityplayer);
-+ c.a(entityplayer);
-+ // Paper end
- }
- // CraftBukkit end
- }
-@@ -424,6 +434,15 @@ public class PlayerChunkMap {
- }
- }
- }
-+
-+ void shutdown() {
-+ getChunks().values().forEach(pchunk -> {
-+ PaperAsyncChunkProvider.CancellableChunkRequest chunkRequest = pchunk.chunkRequest;
-+ if (chunkRequest != null) {
-+ chunkRequest.cancel();
-+ }
-+ });
-+ }
- // Paper end
-
- private void e() {
-diff --git a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
-index de6dd4fed7..da6df06d84 100644
---- a/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
-+++ b/src/main/java/net/minecraft/server/RegionLimitedWorldAccess.java
-@@ -34,7 +34,7 @@ public class RegionLimitedWorldAccess implements GeneratorAccess {
- this.d = l;
- this.e = i;
- this.f = j;
-- this.g = world;
-+ this.g = world.regionLimited(this); // Paper
- this.h = world.getSeed();
- this.m = world.getChunkProvider().getChunkGenerator().getSettings();
- this.i = world.getSeaLevel();
-diff --git a/src/main/java/net/minecraft/server/SchedulerBatch.java b/src/main/java/net/minecraft/server/SchedulerBatch.java
-index 8e909d9caf..f214a74a29 100644
---- a/src/main/java/net/minecraft/server/SchedulerBatch.java
-+++ b/src/main/java/net/minecraft/server/SchedulerBatch.java
-@@ -10,6 +10,7 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
- private final Scheduler<K, T, R> b;
- private boolean c;
- private int d = 1000;
-+ private final java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
-
- public SchedulerBatch(Scheduler<K, T, R> scheduler) {
- this.b = scheduler;
-@@ -19,8 +20,10 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
- this.b.b();
- }
-
-+ public void startBatch() { b(); } // Paper - OBFHELPER
- public void b() {
-- if (this.c) {
-+ lock.lock(); // Paper
-+ if (false && this.c) { // Paper
- throw new RuntimeException("Batch already started.");
- } else {
- this.d = 1000;
-@@ -28,6 +31,7 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
- }
- }
-
-+ public CompletableFuture<R> add(K k0) { return a(k0); } // Paper - OBFHELPER
- public CompletableFuture<R> a(K k0) {
- if (!this.c) {
- throw new RuntimeException("Batch not properly started. Please use startBatch to create a new batch.");
-@@ -44,8 +48,14 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
- }
- }
-
-+ public CompletableFuture<R> executeBatch() { return c(); } // Paper - OBFHELPER
- public CompletableFuture<R> c() {
-- if (!this.c) {
-+ // Paper start
-+ if (!lock.isHeldByCurrentThread()) {
-+ throw new IllegalStateException("Current thread does not hold the write lock");
-+ }
-+ try {// Paper end
-+ if (false && !this.c) { // Paper
- throw new RuntimeException("Batch not properly started. Please use startBatch to create a new batch.");
- } else {
- if (this.d != 1000) {
-@@ -55,5 +65,6 @@ public class SchedulerBatch<K, T extends SchedulerTask<K, T>, R> {
- this.c = false;
- return this.b.c();
- }
-+ } finally { lock.unlock(); } // Paper
- }
- }
-diff --git a/src/main/java/net/minecraft/server/StructurePiece.java b/src/main/java/net/minecraft/server/StructurePiece.java
-index d9def71353..945a005e90 100644
---- a/src/main/java/net/minecraft/server/StructurePiece.java
-+++ b/src/main/java/net/minecraft/server/StructurePiece.java
-@@ -16,7 +16,7 @@ public abstract class StructurePiece {
- private EnumBlockMirror b;
- private EnumBlockRotation c;
- protected int o;
-- private static final Set<Block> d = ImmutableSet.builder().add(Blocks.NETHER_BRICK_FENCE).add(Blocks.TORCH).add(Blocks.WALL_TORCH).add(Blocks.OAK_FENCE).add(Blocks.SPRUCE_FENCE).add(Blocks.DARK_OAK_FENCE).add(Blocks.ACACIA_FENCE).add(Blocks.BIRCH_FENCE).add(Blocks.JUNGLE_FENCE).add(Blocks.LADDER).add(Blocks.IRON_BARS).build();
-+ private static final Set<Block> d = ImmutableSet.<Block>builder().add(Blocks.NETHER_BRICK_FENCE).add(Blocks.TORCH).add(Blocks.WALL_TORCH).add(Blocks.OAK_FENCE).add(Blocks.SPRUCE_FENCE).add(Blocks.DARK_OAK_FENCE).add(Blocks.ACACIA_FENCE).add(Blocks.BIRCH_FENCE).add(Blocks.JUNGLE_FENCE).add(Blocks.LADDER).add(Blocks.IRON_BARS).build(); // Paper - decompile error
-
- public StructurePiece() {}
-
-@@ -66,9 +66,11 @@ public abstract class StructurePiece {
- }
-
- public static StructurePiece a(List<StructurePiece> list, StructureBoundingBox structureboundingbox) {
-+ StructurePiece structurepiece; // Paper
-+ synchronized (list) { // Paper - synchronize main structure list
- Iterator iterator = list.iterator();
-
-- StructurePiece structurepiece;
-+ //StructurePiece structurepiece; // Paper - move up
-
- do {
- if (!iterator.hasNext()) {
-@@ -77,7 +79,7 @@ public abstract class StructurePiece {
-
- structurepiece = (StructurePiece) iterator.next();
- } while (structurepiece.d() == null || !structurepiece.d().a(structureboundingbox));
--
-+ } // Paper
- return structurepiece;
- }
-
-diff --git a/src/main/java/net/minecraft/server/StructureStart.java b/src/main/java/net/minecraft/server/StructureStart.java
-index 284e96710a..8b08efe1f4 100644
---- a/src/main/java/net/minecraft/server/StructureStart.java
-+++ b/src/main/java/net/minecraft/server/StructureStart.java
-@@ -7,7 +7,7 @@ import java.util.Random;
-
- public abstract class StructureStart {
-
-- protected final List<StructurePiece> a = Lists.newArrayList();
-+ protected final List<StructurePiece> a = java.util.Collections.synchronizedList(Lists.newArrayList()); // Paper
- protected StructureBoundingBox b;
- protected int c;
- protected int d;
-@@ -51,13 +51,14 @@ public abstract class StructureStart {
-
- protected void a(IBlockAccess iblockaccess) {
- this.b = StructureBoundingBox.a();
-+ synchronized (this.a) { // Paper - synchronize
- Iterator iterator = this.a.iterator();
-
- while (iterator.hasNext()) {
- StructurePiece structurepiece = (StructurePiece) iterator.next();
-
- this.b.b(structurepiece.d());
-- }
-+ }} // Paper
-
- }
-
-@@ -126,13 +127,14 @@ public abstract class StructureStart {
- int l = k - this.b.e;
-
- this.b.a(0, l, 0);
-+ synchronized (this.a) { // Paper - synchronize
- Iterator iterator = this.a.iterator();
-
- while (iterator.hasNext()) {
- StructurePiece structurepiece = (StructurePiece) iterator.next();
-
- structurepiece.a(0, l, 0);
-- }
-+ }} // Paper
-
- }
-
-@@ -149,13 +151,14 @@ public abstract class StructureStart {
- int i1 = l - this.b.b;
-
- this.b.a(0, i1, 0);
-+ synchronized (this.a) {
- Iterator iterator = this.a.iterator();
-
- while (iterator.hasNext()) {
- StructurePiece structurepiece = (StructurePiece) iterator.next();
-
- structurepiece.a(0, i1, 0);
-- }
-+ }} // Paper
-
- }
-
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index 0d45e21573..97a0fbd55c 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -41,7 +41,7 @@ import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
- import org.bukkit.generator.ChunkGenerator;
- // CraftBukkit end
-
--public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAccess, AutoCloseable {
-+public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAccess, AutoCloseable, Cloneable { // Paper
-
- protected static final Logger e = LogManager.getLogger();
- private static final EnumDirection[] a = EnumDirection.values();
-@@ -104,6 +104,24 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- protected PersistentVillage villages;
- public final MethodProfiler methodProfiler;
- public final boolean isClientSide;
-+ // Paper start - yes this is hacky as shit
-+ RegionLimitedWorldAccess regionLimited;
-+ World originalWorld;
-+ public World regionLimited(RegionLimitedWorldAccess limitedWorldAccess) {
-+ try {
-+ World clone = (World) super.clone();
-+ clone.regionLimited = limitedWorldAccess;
-+ clone.originalWorld = this;
-+ return clone;
-+ } catch (CloneNotSupportedException e1) {
-+ }
-+ return null;
-+ }
-+ ChunkCoordIntPair[] strongholdCoords;
-+ final java.util.concurrent.atomic.AtomicBoolean
-+ strongholdInit = new java.util.concurrent.atomic.AtomicBoolean
-+ (false);
-+ // Paper end
- public boolean allowMonsters;
- public boolean allowAnimals;
- private boolean J;
-@@ -744,17 +762,42 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
-
- }
-
-- public IBlockData getType(BlockPosition blockposition) {
-- // CraftBukkit start - tree generation
-+ // Paper - async variant
-+ public java.util.concurrent.CompletableFuture<IBlockData> getTypeAsync(BlockPosition blockposition) {
-+ int x = blockposition.getX();
-+ int z = blockposition.getZ();
- if (captureTreeGeneration) {
- Iterator<CraftBlockState> it = capturedBlockStates.iterator();
- while (it.hasNext()) {
- CraftBlockState previous = it.next();
-- if (previous.getPosition().equals(blockposition)) {
-- return previous.getHandle();
-+ if (previous.getX() == x && previous.getY() == blockposition.getY() && previous.getZ() == z) {
-+ return java.util.concurrent.CompletableFuture.completedFuture(previous.getHandle());
- }
- }
- }
-+ if (blockposition.isInvalidYLocation()) {
-+ return java.util.concurrent.CompletableFuture.completedFuture(Blocks.VOID_AIR.getBlockData());
-+ } else {
-+ java.util.concurrent.CompletableFuture<IBlockData> future = new java.util.concurrent.CompletableFuture<>();
-+ ((ChunkProviderServer) chunkProvider).getChunkAt(x << 4, z << 4, true, true, (chunk) -> {
-+ future.complete(chunk.getType(blockposition));
-+ });
-+ return future;
-+ }
-+ }
-+ // Paper end
-+
-+ public IBlockData getType(BlockPosition blockposition) {
-+ // CraftBukkit start - tree generation
-+ if (captureTreeGeneration) { // If any of this logic updates, update async variant above
-+ Iterator<CraftBlockState> it = capturedBlockStates.iterator();
-+ while (it.hasNext()) { // If any of this logic updates, update async variant above
-+ CraftBlockState previous = it.next();
-+ if (previous.getX() == blockposition.getX() && previous.getY() == blockposition.getY() && previous.getZ() == blockposition.getZ()) {
-+ return previous.getHandle(); // If any of this logic updates, update async variant above
-+ }
-+ } // If any of this logic updates, update async variant above
-+ }
- // CraftBukkit end
- if (blockposition.isInvalidYLocation()) { // Paper
- return Blocks.VOID_AIR.getBlockData();
-@@ -1023,6 +1066,11 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- }
-
- public boolean addEntity(Entity entity, SpawnReason spawnReason) { // Changed signature, added SpawnReason
-+ // Paper start
-+ if (regionLimited != null) {
-+ return regionLimited.addEntity(entity, spawnReason);
-+ }
-+ // Paper end
- org.spigotmc.AsyncCatcher.catchOp( "entity add"); // Spigot
- if (entity.valid) { MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); return true; } // Paper
- if (!CraftEventFactory.doEntityAddEventCalling(this, entity, spawnReason)) {
-diff --git a/src/main/java/net/minecraft/server/WorldGenStronghold.java b/src/main/java/net/minecraft/server/WorldGenStronghold.java
-index 69d8a25bdf..d0eaa9e9f8 100644
---- a/src/main/java/net/minecraft/server/WorldGenStronghold.java
-+++ b/src/main/java/net/minecraft/server/WorldGenStronghold.java
-@@ -10,23 +10,28 @@ import javax.annotation.Nullable;
-
- public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrongholdConfiguration> {
-
-- private boolean b;
-- private ChunkCoordIntPair[] c;
-- private long d;
-+ // Paper start - no shared state
-+ //private boolean b;
-+ //private ChunkCoordIntPair[] c;
-+ //private long d;
-+ // Paper end
-
- public WorldGenStronghold() {}
-
- protected boolean a(ChunkGenerator<?> chunkgenerator, Random random, int i, int j) {
-- if (this.d != chunkgenerator.getSeed()) {
-+ // Paper start
-+ /*if (this.d != chunkgenerator.getSeed()) {
- this.c();
-- }
-+ }*/
-
-- if (!this.b) {
-+ synchronized (chunkgenerator.getWorld().strongholdInit) {
-+ if (chunkgenerator.getWorld().strongholdInit.compareAndSet(false, true)) { // Paper
- this.a(chunkgenerator);
-- this.b = true;
-- }
-+ //this.b = true;
-+ }} // Paper
-+ // Paper end
-
-- ChunkCoordIntPair[] achunkcoordintpair = this.c;
-+ ChunkCoordIntPair[] achunkcoordintpair = chunkgenerator.getWorld().strongholdCoords; // Paper
- int k = achunkcoordintpair.length;
-
- for (int l = 0; l < k; ++l) {
-@@ -41,8 +46,8 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- }
-
- private void c() {
-- this.b = false;
-- this.c = null;
-+ //this.b = false; // Paper
-+ //this.c = null; // Paper
- }
-
- protected boolean a(GeneratorAccess generatoraccess) {
-@@ -76,23 +81,30 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- if (!chunkgenerator.getWorldChunkManager().a(this)) {
- return null;
- } else {
-- if (this.d != world.getSeed()) {
-- this.c();
-- }
-+ // Paper start
-+ /*if (this.d != chunkgenerator.getSeed()) {
-+ this.c();
-+ }*/
-
-- if (!this.b) {
-- this.a(chunkgenerator);
-- this.b = true;
-- }
-+ synchronized (chunkgenerator.getWorld().strongholdInit) {
-+ if (chunkgenerator.getWorld().strongholdInit.compareAndSet(false, true)) { // Paper
-+ this.a(chunkgenerator);
-+ //this.b = true;
-+ }} // Paper
-+ // Paper end
-
- BlockPosition blockposition1 = null;
- BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition(0, 0, 0);
- double d0 = Double.MAX_VALUE;
-+ // Paper start
-+ /*
- ChunkCoordIntPair[] achunkcoordintpair = this.c;
- int j = achunkcoordintpair.length;
-
- for (int k = 0; k < j; ++k) {
-- ChunkCoordIntPair chunkcoordintpair = achunkcoordintpair[k];
-+ */
-+ for (ChunkCoordIntPair chunkcoordintpair : world.strongholdCoords) {
-+ // Paper end
-
- blockposition_mutableblockposition.c((chunkcoordintpair.x << 4) + 8, 32, (chunkcoordintpair.z << 4) + 8);
- double d1 = blockposition_mutableblockposition.n(blockposition);
-@@ -111,7 +123,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- }
-
- private void a(ChunkGenerator<?> chunkgenerator) {
-- this.d = chunkgenerator.getSeed();
-+ //this.d = chunkgenerator.getSeed(); // Paper
- List<BiomeBase> list = Lists.newArrayList();
- Iterator iterator = IRegistry.BIOME.iterator();
-
-@@ -127,7 +139,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- int j = chunkgenerator.getSettings().f();
- int k = chunkgenerator.getSettings().g();
-
-- this.c = new ChunkCoordIntPair[j];
-+ ChunkCoordIntPair[] strongholdCoords = chunkgenerator.getWorld().strongholdCoords = new ChunkCoordIntPair[j]; // Paper
- int l = 0;
- Long2ObjectMap<StructureStart> long2objectmap = chunkgenerator.getStructureStartCache(this);
-
-@@ -137,8 +149,8 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- while (objectiterator.hasNext()) {
- StructureStart structurestart = (StructureStart) objectiterator.next();
-
-- if (l < this.c.length) {
-- this.c[l++] = new ChunkCoordIntPair(structurestart.e(), structurestart.f());
-+ if (l < strongholdCoords.length) { // Paper
-+ strongholdCoords[l++] = new ChunkCoordIntPair(structurestart.e(), structurestart.f()); // Paper
- }
- }
- }
-@@ -149,11 +161,11 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- double d0 = random.nextDouble() * 3.141592653589793D * 2.0D;
- int i1 = long2objectmap.size();
-
-- if (i1 < this.c.length) {
-+ if (i1 < strongholdCoords.length) { // Paper
- int j1 = 0;
- int k1 = 0;
-
-- for (int l1 = 0; l1 < this.c.length; ++l1) {
-+ for (int l1 = 0; l1 < strongholdCoords.length; ++l1) { // Paper
- double d1 = (double) (4 * i + i * k1 * 6) + (random.nextDouble() - 0.5D) * (double) i * 2.5D;
- int i2 = (int) Math.round(Math.cos(d0) * d1);
- int j2 = (int) Math.round(Math.sin(d0) * d1);
-@@ -165,7 +177,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- }
-
- if (l1 >= i1) {
-- this.c[l1] = new ChunkCoordIntPair(i2, j2);
-+ strongholdCoords[l1] = new ChunkCoordIntPair(i2, j2); // Paper
- }
-
- d0 += 6.283185307179586D / (double) k;
-@@ -174,7 +186,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureStrong
- ++k1;
- j1 = 0;
- k += 2 * k / (k1 + 1);
-- k = Math.min(k, this.c.length - l1);
-+ k = Math.min(k, strongholdCoords.length - l1); // Paper
- d0 += random.nextDouble() * 3.141592653589793D * 2.0D;
- }
- }
-diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
-index 0ff3fe03dd..e71a405807 100644
---- a/src/main/java/net/minecraft/server/WorldServer.java
-+++ b/src/main/java/net/minecraft/server/WorldServer.java
-@@ -80,6 +80,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
- this.P();
- this.Q();
- this.getWorldBorder().a(minecraftserver.au());
-+ MCUtil.scheduleAsyncTask(() -> this.getChunkProvider().chunkLoader.getPersistentStructureLegacy(dimensionmanager, worldMaps)); // Paper
- }
-
- public WorldServer i_() {
-@@ -714,7 +715,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
- gen = new org.bukkit.craftbukkit.generator.NormalChunkGenerator(this, this.getSeed());
- }
-
-- return new ChunkProviderServer(this, ichunkloader, gen, this.server);
-+ return com.destroystokyo.paper.PaperConfig.asyncChunks ? new PaperAsyncChunkProvider(this, ichunkloader, gen, this.server) : new ChunkProviderServer(this, ichunkloader, gen, this.server); // Paper - async chunks
- // CraftBukkit end
- }
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index d4d8fbb556..af065bd705 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-@@ -1022,8 +1022,12 @@ public final class CraftServer implements Server {
- if (internal.getWorld().getKeepSpawnInMemory()) {
- short short1 = internal.paperConfig.keepLoadedRange; // Paper
- long i = System.currentTimeMillis();
-- for (int j = -short1; j <= short1; j += 16) {
-- for (int k = -short1; k <= short1; k += 16) {
-+ // Paper start
-+ for (ChunkCoordIntPair coords : internal.getChunkProvider().getSpiralOutChunks(internal.getSpawn(), short1 >> 4)) {{
-+ int j = coords.x;
-+ int k = coords.z;
-+ // Paper end
-+
- long l = System.currentTimeMillis();
-
- if (l < i) {
-@@ -1039,7 +1043,7 @@ public final class CraftServer implements Server {
- }
-
- BlockPosition chunkcoordinates = internal.getSpawn();
-- internal.getChunkProvider().getChunkAt(chunkcoordinates.getX() + j >> 4, chunkcoordinates.getZ() + k >> 4, true, true);
-+ internal.getChunkProvider().getChunkAt(chunkcoordinates.getX() + j >> 4, chunkcoordinates.getZ() + k >> 4, true, true, c -> {}); // Paper
- }
- }
- }
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 0e4455d66e..eacecccfdb 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -163,6 +163,16 @@ public class CraftWorld implements World {
- }
- }
-
-+ // Paper start - Async chunk load API
-+ @Override
-+ public java.util.concurrent.CompletableFuture<Chunk> getChunkAtAsync(final int x, final int z, final boolean gen) {
-+ final ChunkProviderServer cps = this.world.getChunkProvider();
-+ java.util.concurrent.CompletableFuture<Chunk> future = new java.util.concurrent.CompletableFuture<>();
-+ cps.getChunkAt(x, z, true, gen, chunk -> future.complete(chunk != null ? chunk.bukkitChunk : null));
-+ return future;
-+ }
-+ // Paper end
-+
- public Chunk getChunkAt(int x, int z) {
- return this.world.getChunkProvider().getChunkAt(x, z, true, true).bukkitChunk;
- }
-@@ -1466,10 +1476,13 @@ public class CraftWorld implements World {
- int chunkCoordZ = chunkcoordinates.getZ() >> 4;
- // Cycle through the 25x25 Chunks around it to load/unload the chunks.
- int radius = world.paperConfig.keepLoadedRange / 16; // Paper
-- for (int x = -radius; x <= radius; x++) { // Paper
-- for (int z = -radius; z <= radius; z++) { // Paper
-+ // Paper start
-+ for (ChunkCoordIntPair coords : world.getChunkProvider().getSpiralOutChunks(world.getSpawn(), radius)) {{
-+ int x = coords.x;
-+ int z = coords.z;
-+ // Paper end
- if (keepLoaded) {
-- loadChunk(chunkCoordX + x, chunkCoordZ + z);
-+ getChunkAtAsync(chunkCoordX + x, chunkCoordZ + z, chunk -> {}); // Paper - Async Chunks
- } else {
- if (isChunkLoaded(chunkCoordX + x, chunkCoordZ + z)) {
- unloadChunk(chunkCoordX + x, chunkCoordZ + z);
-diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
-index 68e30185a2..7905420cac 100644
---- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
-+++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java
-@@ -82,6 +82,7 @@ public class CraftEventFactory {
- public static final DamageSource POISON = CraftDamageSource.copyOf(DamageSource.MAGIC);
- public static org.bukkit.block.Block blockDamage; // For use in EntityDamageByBlockEvent
- public static Entity entityDamage; // For use in EntityDamageByEntityEvent
-+ public static boolean isWorldGen(GeneratorAccess world) { return world instanceof net.minecraft.server.RegionLimitedWorldAccess; } // Paper
-
- // helper methods
- private static boolean canBuild(CraftWorld world, Player player, int x, int z) {
-@@ -474,6 +475,7 @@ public class CraftEventFactory {
- CraftServer craftServer = (CraftServer) entity.getServer();
-
- CreatureSpawnEvent event = new CreatureSpawnEvent(entity, spawnReason);
-+ if (isWorldGen(entityliving.world)) return event; // Paper - do not call during world gen
- craftServer.getPluginManager().callEvent(event);
- return event;
- }
-@@ -1128,6 +1130,7 @@ public class CraftEventFactory {
- }
-
- BlockIgniteEvent event = new BlockIgniteEvent(bukkitWorld.getBlockAt(block.getX(), block.getY(), block.getZ()), cause, igniter);
-+ if (isWorldGen(world)) return event; // Paper - do not call during world gen
- world.getServer().getPluginManager().callEvent(event);
- return event;
- }
-@@ -1152,6 +1155,7 @@ public class CraftEventFactory {
- }
-
- BlockIgniteEvent event = new BlockIgniteEvent(bukkitWorld.getBlockAt(pos.getX(), pos.getY(), pos.getZ()), cause, bukkitIgniter);
-+ if (isWorldGen(world)) return event; // Paper - do not call during world gen
- world.getServer().getPluginManager().callEvent(event);
- return event;
- }
-@@ -1359,7 +1363,8 @@ public class CraftEventFactory {
- public static BlockPhysicsEvent callBlockPhysicsEvent(GeneratorAccess world, BlockPosition blockposition) {
- org.bukkit.block.Block block = CraftBlock.at(world, blockposition);
- BlockPhysicsEvent event = new BlockPhysicsEvent(block, block.getBlockData());
-- world.getMinecraftWorld().getMinecraftServer().server.getPluginManager().callEvent(event);
-+ if (isWorldGen(world)) return event; // Paper - do not call during world gen
-+ Bukkit.getPluginManager().callEvent(event); // Paper
- return event;
- }
-
-@@ -1395,6 +1400,7 @@ public class CraftEventFactory {
- }
-
- EntityPotionEffectEvent event = new EntityPotionEffectEvent((LivingEntity) entity.getBukkitEntity(), bukkitOldEffect, bukkitNewEffect, cause, action, willOverride);
-+ if (isWorldGen(entity.world)) return event; // Paper - do not call during world gen
- Bukkit.getPluginManager().callEvent(event);
-
- return event;
-@@ -1413,6 +1419,7 @@ public class CraftEventFactory {
- blockState.setData(block);
-
- BlockFormEvent event = (entity == null) ? new BlockFormEvent(blockState.getBlock(), blockState) : new EntityBlockFormEvent(entity.getBukkitEntity(), blockState.getBlock(), blockState);
-+ if (isWorldGen(world)) return true; // Paper - do not call during world gen
- world.getServer().getPluginManager().callEvent(event);
-
- if (!event.isCancelled()) {
-diff --git a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-index 9e553866eb..04c0adf7c7 100644
---- a/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-+++ b/src/main/java/org/bukkit/craftbukkit/generator/CustomChunkGenerator.java
-@@ -21,6 +21,7 @@ public class CustomChunkGenerator extends InternalChunkGenerator<GeneratorSettin
- private final WorldServer world;
- private final long seed;
- private final Random random;
-+ public final boolean asyncSupported; // Paper
- private final WorldChunkManager chunkManager;
- private final WorldGenStronghold strongholdGen = new WorldGenStronghold();
- private final GeneratorSettingsDefault settings = new GeneratorSettingsDefault();
-@@ -43,6 +44,15 @@ public class CustomChunkGenerator extends InternalChunkGenerator<GeneratorSettin
- this.world = (WorldServer) world;
- this.generator = generator;
- this.seed = seed;
-+ // Paper start
-+ boolean asyncSupported = false;
-+ try {
-+ java.lang.reflect.Field asyncSafe = generator.getClass().getDeclaredField("PAPER_ASYNC_SAFE");
-+ asyncSafe.setAccessible(true);
-+ asyncSupported = asyncSafe.getBoolean(generator);
-+ } catch (NoSuchFieldException | IllegalAccessException ignored) {}
-+ this.asyncSupported = asyncSupported;
-+ // Paper end
-
- this.random = new Random(seed);
- this.chunkManager = world.worldProvider.getChunkGenerator().getWorldChunkManager();
---
-2.21.0
-
diff --git a/patches/removed/1.14/0363-Optimize-Light-Recalculations.patch b/patches/removed/1.14/0363-Optimize-Light-Recalculations.patch
deleted file mode 100644
index 6f1019924d..0000000000
--- a/patches/removed/1.14/0363-Optimize-Light-Recalculations.patch
+++ /dev/null
@@ -1,52 +0,0 @@
-From 68d53d355f81300b2cbd41c12ef16efe87a3d8fb Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Fri, 28 Sep 2018 20:46:29 -0400
-Subject: [PATCH] Optimize Light Recalculations
-
-Optimizes to not repeatedly look up the same chunk for
-light lookups.
-
-diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
-index 4f64072a7b..966879a894 100644
---- a/src/main/java/net/minecraft/server/Chunk.java
-+++ b/src/main/java/net/minecraft/server/Chunk.java
-@@ -353,7 +353,7 @@ public class Chunk implements IChunkAccess {
- private void a(int i, int j, int k, int l) {
- if (l > k && this.areNeighborsLoaded(1)) { // Paper
- for (int i1 = k; i1 < l; ++i1) {
-- this.world.c(EnumSkyBlock.SKY, new BlockPosition(i, i1, j));
-+ this.world.updateBrightness(EnumSkyBlock.SKY, new BlockPosition(i, i1, j), this); // Paper
- }
-
- this.x = true;
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index 97a0fbd55c..fb71879ac0 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -591,8 +591,9 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- }
-
- if (this.worldProvider.g()) {
-- for (i1 = k; i1 <= l; ++i1) {
-- this.c(EnumSkyBlock.SKY, new BlockPosition(i, i1, j));
-+ Chunk chunk = getChunkIfLoaded(i >> 4, j >> 4); // Paper
-+ for (i1 = k; chunk != null && i1 <= l; ++i1) { // Paper
-+ this.updateBrightness(EnumSkyBlock.SKY, new BlockPosition(i, i1, j), chunk); // Paper
- }
- }
-
-@@ -2220,6 +2221,11 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- public boolean c(EnumSkyBlock enumskyblock, BlockPosition blockposition) {
- // CraftBukkit start - Use neighbor cache instead of looking up
- Chunk chunk = this.getChunkIfLoaded(blockposition.getX() >> 4, blockposition.getZ() >> 4);
-+ // Paper start - optimize light updates where chunk is known
-+ return updateBrightness(enumskyblock, blockposition, chunk);
-+ }
-+ public boolean updateBrightness(EnumSkyBlock enumskyblock, BlockPosition blockposition, Chunk chunk) {
-+ // Paper end
- if (chunk == null || !chunk.areNeighborsLoaded(1) /*!this.areChunksLoaded(blockposition, 17, false)*/) {
- // CraftBukkit end
- return false;
---
-2.21.0
-
diff --git a/patches/removed/1.14/0366-Fix-Sending-Chunks-to-Client.patch b/patches/removed/1.14/0366-Fix-Sending-Chunks-to-Client.patch
deleted file mode 100644
index 014bdbd6b6..0000000000
--- a/patches/removed/1.14/0366-Fix-Sending-Chunks-to-Client.patch
+++ /dev/null
@@ -1,66 +0,0 @@
-From 07a00d527e871eadaf4a96af5ad367ddcbfa9cc3 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 29 Sep 2018 01:18:16 -0400
-Subject: [PATCH] Fix Sending Chunks to Client
-
-Vanilla has some screwy logic that doesn't send a chunk until
-it has been post processed. This is an issue as post processing
-doesn't occur until all neighbor chunks have been loaded.
-
-This can reduce view distance while generating terrain, but also
-cause bugs where chunks are never sent to the client.
-
-This fix always sends chunks to the client, and simply updates
-the client anytime post processing is triggered with the new chunk data.
-
-diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java
-index 0d51c1baeb..46804203fe 100644
---- a/src/main/java/net/minecraft/server/Chunk.java
-+++ b/src/main/java/net/minecraft/server/Chunk.java
-@@ -1190,7 +1190,7 @@ public class Chunk implements IChunkAccess {
- }
-
- public boolean isReady() {
-- return this.C.a(ChunkStatus.POSTPROCESSED);
-+ return true; // Paper - Always send chunks
- }
-
- public boolean v() {
-@@ -1428,6 +1428,13 @@ public class Chunk implements IChunkAccess {
- this.h.clear();
- this.a(ChunkStatus.POSTPROCESSED);
- this.m.a(this);
-+ // Paper start - resend chunk after post process
-+ PlayerChunk playerChunk = ((WorldServer) world).getPlayerChunkMap().getChunk(locX, locZ);
-+ if (playerChunk != null) {
-+ playerChunk.done = false;
-+ playerChunk.sendAll();
-+ }
-+ // Paper end
- }
- }
-
-diff --git a/src/main/java/net/minecraft/server/PlayerChunk.java b/src/main/java/net/minecraft/server/PlayerChunk.java
-index e4cf8548d3..ac5d158093 100644
---- a/src/main/java/net/minecraft/server/PlayerChunk.java
-+++ b/src/main/java/net/minecraft/server/PlayerChunk.java
-@@ -20,7 +20,7 @@ public class PlayerChunk {
- private int dirtyCount;
- private int h;
- private long i;
-- private boolean done;
-+ boolean done; // Paper - package-private
- boolean chunkExists; // Paper
- // Paper start
- PaperAsyncChunkProvider.CancellableChunkRequest chunkRequest;
-@@ -147,6 +147,7 @@ public class PlayerChunk {
- }
- }
-
-+ public boolean sendAll() { return b(); } // Paper - OBFHELPER
- public boolean b() {
- if (this.done) {
- return true;
---
-2.21.0
-
diff --git a/patches/removed/1.14/0368-Fix-FileIOThread-concurrency-issues.patch b/patches/removed/1.14/0368-Fix-FileIOThread-concurrency-issues.patch
deleted file mode 100644
index f8d48599a9..0000000000
--- a/patches/removed/1.14/0368-Fix-FileIOThread-concurrency-issues.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-From 4e5812633b869d071215da93dc3cffd797de9951 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Wed, 3 Oct 2018 19:04:53 +0100
-Subject: [PATCH] Fix FileIOThread concurrency issues
-
-FileIOThread was using two volatile counters in order to track if
-any pending work was in the queue, this causes potential concurrency
-issues when this counter is updated from multiple threads, potentially
-causing these counters to desync due to the unsafe volatile update
-
-diff --git a/src/main/java/net/minecraft/server/FileIOThread.java b/src/main/java/net/minecraft/server/FileIOThread.java
-index 3c688f546c..570624600d 100644
---- a/src/main/java/net/minecraft/server/FileIOThread.java
-+++ b/src/main/java/net/minecraft/server/FileIOThread.java
-@@ -10,7 +10,7 @@ public class FileIOThread implements Runnable {
-
- private static final Logger a = LogManager.getLogger();
- private static final FileIOThread b = new FileIOThread();
-- private final List<IAsyncChunkSaver> c = Collections.synchronizedList(Lists.newArrayList());
-+ private final List<IAsyncChunkSaver> c = Collections.synchronizedList(Lists.newArrayList()); private List<IAsyncChunkSaver> getThreadedIOQueue() { return c; } // Paper - OBFHELPER
- private volatile long d;
- private volatile long e;
- private volatile boolean f;
-@@ -75,7 +75,7 @@ public class FileIOThread implements Runnable {
- public void b() throws InterruptedException {
- this.f = true;
-
-- while (this.d != this.e) {
-+ while(!this.getThreadedIOQueue().isEmpty()) { // Paper - check actual list size
- Thread.sleep(10L);
- }
-
---
-2.21.0
-
diff --git a/patches/removed/1.14/0377-Optimize-Persistent-Data-Loading.patch b/patches/removed/1.14/0377-Optimize-Persistent-Data-Loading.patch
deleted file mode 100644
index 4e78e4e534..0000000000
--- a/patches/removed/1.14/0377-Optimize-Persistent-Data-Loading.patch
+++ /dev/null
@@ -1,66 +0,0 @@
-From a576811cfbd84b86003b6d81569f8e4399447a10 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Fri, 29 Mar 2019 01:25:11 -0400
-Subject: [PATCH] Optimize Persistent Data Loading
-
-removes Mineshaft loading legacy as we had pre 1.13.2 to avoid managing
-that very large data file from legacy systems.
-
-Previous to 1.13.2 these data files were never loaded to begin with, so they
-effectively do not contain valid/relevant data.
-
-These files take a long time to convert on large worlds and crashes the server.
-
-Additionally, cache the result of a file being missing so we don't keep spam checking it.
-
-diff --git a/src/main/java/net/minecraft/server/WorldPersistentData.java b/src/main/java/net/minecraft/server/WorldPersistentData.java
-index 00e9a17355..153809432c 100644
---- a/src/main/java/net/minecraft/server/WorldPersistentData.java
-+++ b/src/main/java/net/minecraft/server/WorldPersistentData.java
-@@ -26,6 +26,7 @@ public class WorldPersistentData {
- this.c = datafixer;
- this.d = file;
- }
-+ private static final PersistentBase NO_RESULT = new ForcedChunk(); // Paper
-
- private File a(String s) {
- return new File(this.d, s + ".dat");
-@@ -46,14 +47,17 @@ public class WorldPersistentData {
-
- @Nullable
- public <T extends PersistentBase> T b(Supplier<T> supplier, String s) {
-+ if ("Mineshaft_index".equals(s) || "Mineshaft".equals(s)) return null; // Paper - mineshaft is useless data
- T persistentbase = (T) this.data.get(s); // Paper - decompile fix
-
- if (persistentbase == null && !this.data.containsKey(s)) {
- persistentbase = this.c(supplier, s);
- this.data.put(s, persistentbase);
-+ } else { // Paper
-+ this.data.put(s, NO_RESULT); // Paper
- }
-
-- return persistentbase;
-+ return persistentbase == NO_RESULT ? null : persistentbase; // Paper
- }
-
- @Nullable
-@@ -66,7 +70,7 @@ public class WorldPersistentData {
- NBTTagCompound nbttagcompound = this.a(s, SharedConstants.a().getWorldVersion());
-
- t0.a(nbttagcompound.getCompound("data"));
-- return t0;
-+ return t0 == NO_RESULT ? null : t0; // Paper
- }
- } catch (Exception exception) {
- WorldPersistentData.LOGGER.error("Error loading saved data: {}", s, exception);
-@@ -80,6 +84,7 @@ public class WorldPersistentData {
- }
-
- public NBTTagCompound a(String s, int i) throws IOException {
-+ if ("Mineshaft".equals(s) || "Mineshaft_index".equals(s)) return new NBTTagCompound(); // Paper
- File file = this.a(s);
- PushbackInputStream pushbackinputstream = new PushbackInputStream(new FileInputStream(file), 2);
- Throwable throwable = null;
---
-2.22.0
-
diff --git a/patches/removed/1.14/0388-Use-EntityTypes-for-living-entities.patch b/patches/removed/1.14/0388-Use-EntityTypes-for-living-entities.patch
deleted file mode 100644
index 1d43a1d96d..0000000000
--- a/patches/removed/1.14/0388-Use-EntityTypes-for-living-entities.patch
+++ /dev/null
@@ -1,872 +0,0 @@
-From a7f8efc50ad709eeb7991d8a460ed6075115175d Mon Sep 17 00:00:00 2001
-From: BillyGalbreath <[email protected]>
-Date: Thu, 4 Oct 2018 10:08:02 -0500
-Subject: [PATCH] Use EntityTypes for living entities
-
-
-diff --git a/src/main/java/net/minecraft/server/BlockMonsterEggs.java b/src/main/java/net/minecraft/server/BlockMonsterEggs.java
-index 5a0cc6d058..d385f647e7 100644
---- a/src/main/java/net/minecraft/server/BlockMonsterEggs.java
-+++ b/src/main/java/net/minecraft/server/BlockMonsterEggs.java
-@@ -35,7 +35,7 @@ public class BlockMonsterEggs extends Block {
-
- public void dropNaturally(IBlockData iblockdata, World world, BlockPosition blockposition, float f, int i) {
- if (!world.isClientSide && world.getGameRules().getBoolean("doTileDrops")) {
-- EntitySilverfish entitysilverfish = new EntitySilverfish(world);
-+ EntitySilverfish entitysilverfish = EntityTypes.SILVERFISH.create(world); // Paper
-
- entitysilverfish.setPositionRotation((double) blockposition.getX() + 0.5D, (double) blockposition.getY(), (double) blockposition.getZ() + 0.5D, 0.0F, 0.0F);
- world.addEntity(entitysilverfish, SpawnReason.SILVERFISH_BLOCK); // CraftBukkit - add SpawnReason
-diff --git a/src/main/java/net/minecraft/server/BlockPumpkinCarved.java b/src/main/java/net/minecraft/server/BlockPumpkinCarved.java
-index 75622fbdf8..2653699840 100644
---- a/src/main/java/net/minecraft/server/BlockPumpkinCarved.java
-+++ b/src/main/java/net/minecraft/server/BlockPumpkinCarved.java
-@@ -52,7 +52,7 @@ public class BlockPumpkinCarved extends BlockFacingHorizontal {
- blockList.setTypeAndData(shapedetectorblock1.getPosition(), Blocks.AIR.getBlockData(), 2); // CraftBukkit
- }
-
-- EntitySnowman entitysnowman = new EntitySnowman(world);
-+ EntitySnowman entitysnowman = EntityTypes.SNOW_GOLEM.create(world); // Paper
- BlockPosition blockposition1 = shapedetector_shapedetectorcollection.a(0, 2, 0).getPosition();
-
- entitysnowman.setPositionRotation((double) blockposition1.getX() + 0.5D, (double) blockposition1.getY() + 0.05D, (double) blockposition1.getZ() + 0.5D, 0.0F, 0.0F);
-@@ -87,7 +87,7 @@ public class BlockPumpkinCarved extends BlockFacingHorizontal {
- }
-
- BlockPosition blockposition2 = shapedetector_shapedetectorcollection.a(1, 2, 0).getPosition();
-- EntityIronGolem entityirongolem = new EntityIronGolem(world);
-+ EntityIronGolem entityirongolem = EntityTypes.IRON_GOLEM.create(world); // Paper
-
- entityirongolem.setPlayerCreated(true);
- entityirongolem.setPositionRotation((double) blockposition2.getX() + 0.5D, (double) blockposition2.getY() + 0.05D, (double) blockposition2.getZ() + 0.5D, 0.0F, 0.0F);
-diff --git a/src/main/java/net/minecraft/server/BlockTurtleEgg.java b/src/main/java/net/minecraft/server/BlockTurtleEgg.java
-index 0f0872c1e0..1c1bf85a0e 100644
---- a/src/main/java/net/minecraft/server/BlockTurtleEgg.java
-+++ b/src/main/java/net/minecraft/server/BlockTurtleEgg.java
-@@ -94,7 +94,7 @@ public class BlockTurtleEgg extends Block {
- if (!world.isClientSide) {
- for (int j = 0; j < (Integer) iblockdata.get(BlockTurtleEgg.b); ++j) {
- world.triggerEffect(2001, blockposition, Block.getCombinedId(iblockdata));
-- EntityTurtle entityturtle = new EntityTurtle(world);
-+ EntityTurtle entityturtle = EntityTypes.TURTLE.create(world); // Paper
-
- entityturtle.setAgeRaw(-24000);
- entityturtle.g(blockposition);
-diff --git a/src/main/java/net/minecraft/server/BlockWitherSkull.java b/src/main/java/net/minecraft/server/BlockWitherSkull.java
-index 93bf32dc1a..e6063bb462 100644
---- a/src/main/java/net/minecraft/server/BlockWitherSkull.java
-+++ b/src/main/java/net/minecraft/server/BlockWitherSkull.java
-@@ -52,7 +52,7 @@ public class BlockWitherSkull extends BlockSkull {
- }
-
- BlockPosition blockposition1 = shapedetector_shapedetectorcollection.a(1, 0, 0).getPosition();
-- EntityWither entitywither = new EntityWither(world);
-+ EntityWither entitywither = EntityTypes.WITHER.create(world); // Paper
- BlockPosition blockposition2 = shapedetector_shapedetectorcollection.a(1, 2, 0).getPosition();
-
- entitywither.setPositionRotation((double) blockposition2.getX() + 0.5D, (double) blockposition2.getY() + 0.55D, (double) blockposition2.getZ() + 0.5D, shapedetector_shapedetectorcollection.getFacing().k() == EnumDirection.EnumAxis.X ? 0.0F : 90.0F, 0.0F);
-diff --git a/src/main/java/net/minecraft/server/EnderDragonBattle.java b/src/main/java/net/minecraft/server/EnderDragonBattle.java
-index aad7ce93f6..09eabf1235 100644
---- a/src/main/java/net/minecraft/server/EnderDragonBattle.java
-+++ b/src/main/java/net/minecraft/server/EnderDragonBattle.java
-@@ -412,7 +412,7 @@ public class EnderDragonBattle {
-
- private EntityEnderDragon n() {
- this.d.getChunkAtWorldCoords(new BlockPosition(0, 128, 0));
-- EntityEnderDragon entityenderdragon = new EntityEnderDragon(this.d);
-+ EntityEnderDragon entityenderdragon = EntityTypes.ENDER_DRAGON.create(this.d); // Paper
-
- entityenderdragon.getDragonControllerManager().setControllerPhase(DragonControllerPhase.HOLDING_PATTERN);
- entityenderdragon.setPositionRotation(0.0D, 128.0D, 0.0D, this.d.random.nextFloat() * 360.0F, 0.0F);
-diff --git a/src/main/java/net/minecraft/server/EntityChicken.java b/src/main/java/net/minecraft/server/EntityChicken.java
-index ee159e0a81..070a9e7b14 100644
---- a/src/main/java/net/minecraft/server/EntityChicken.java
-+++ b/src/main/java/net/minecraft/server/EntityChicken.java
-@@ -96,7 +96,7 @@ public class EntityChicken extends EntityAnimal {
- }
-
- public EntityChicken createChild(EntityAgeable entityageable) {
-- return new EntityChicken(this.world);
-+ return EntityTypes.CHICKEN.create(world); // Paper
- }
-
- public boolean f(ItemStack itemstack) {
-diff --git a/src/main/java/net/minecraft/server/EntityCow.java b/src/main/java/net/minecraft/server/EntityCow.java
-index 5874d2993c..cc53e915d7 100644
---- a/src/main/java/net/minecraft/server/EntityCow.java
-+++ b/src/main/java/net/minecraft/server/EntityCow.java
-@@ -88,7 +88,7 @@ public class EntityCow extends EntityAnimal {
- }
-
- public EntityCow createChild(EntityAgeable entityageable) {
-- return new EntityCow(this.world);
-+ return EntityTypes.COW.create(world); // Paper
- }
-
- public float getHeadHeight() {
-diff --git a/src/main/java/net/minecraft/server/EntityEnderPearl.java b/src/main/java/net/minecraft/server/EntityEnderPearl.java
-index 961afa5c42..a372f6508f 100644
---- a/src/main/java/net/minecraft/server/EntityEnderPearl.java
-+++ b/src/main/java/net/minecraft/server/EntityEnderPearl.java
-@@ -74,7 +74,7 @@ public class EntityEnderPearl extends EntityProjectile {
-
- if (!teleEvent.isCancelled() && !entityplayer.playerConnection.isDisconnected()) {
- if (this.random.nextFloat() < 0.05F && this.world.getGameRules().getBoolean("doMobSpawning")) {
-- EntityEndermite entityendermite = new EntityEndermite(this.world);
-+ EntityEndermite entityendermite = EntityTypes.ENDERMITE.create(world); // Paper
-
- entityendermite.setPlayerSpawned(true);
- entityendermite.setPositionRotation(entityliving.locX, entityliving.locY, entityliving.locZ, entityliving.yaw, entityliving.pitch);
-diff --git a/src/main/java/net/minecraft/server/EntityEvoker.java b/src/main/java/net/minecraft/server/EntityEvoker.java
-index 963b6fbb9c..fc20bbe272 100644
---- a/src/main/java/net/minecraft/server/EntityEvoker.java
-+++ b/src/main/java/net/minecraft/server/EntityEvoker.java
-@@ -188,8 +188,7 @@ public class EntityEvoker extends EntityIllagerWizard {
- protected void j() {
- for (int i = 0; i < 3; ++i) {
- BlockPosition blockposition = (new BlockPosition(EntityEvoker.this)).a(-2 + EntityEvoker.this.random.nextInt(5), 1, -2 + EntityEvoker.this.random.nextInt(5));
-- EntityVex entityvex = new EntityVex(EntityEvoker.this.world);
--
-+ EntityVex entityvex = EntityTypes.VEX.create(EntityEvoker.this.world); // Paper
- entityvex.setPositionRotation(blockposition, 0.0F, 0.0F);
- entityvex.prepare(EntityEvoker.this.world.getDamageScaler(blockposition), (GroupDataEntity) null, (NBTTagCompound) null);
- entityvex.a((EntityInsentient) EntityEvoker.this);
-diff --git a/src/main/java/net/minecraft/server/EntityHorse.java b/src/main/java/net/minecraft/server/EntityHorse.java
-index 4e8a97c557..1b9425f3e6 100644
---- a/src/main/java/net/minecraft/server/EntityHorse.java
-+++ b/src/main/java/net/minecraft/server/EntityHorse.java
-@@ -208,11 +208,11 @@ public class EntityHorse extends EntityHorseAbstract {
- Object object;
-
- if (entityageable instanceof EntityHorseDonkey) {
-- object = new EntityHorseMule(this.world);
-+ object = EntityTypes.MULE.create(world); // Paper
- } else {
- EntityHorse entityhorse = (EntityHorse) entityageable;
-
-- object = new EntityHorse(this.world);
-+ object = EntityTypes.HORSE.create(world); // Paper
- int i = this.random.nextInt(9);
- int j;
-
-diff --git a/src/main/java/net/minecraft/server/EntityHorseDonkey.java b/src/main/java/net/minecraft/server/EntityHorseDonkey.java
-index 72eed22eb9..65c40e72bf 100644
---- a/src/main/java/net/minecraft/server/EntityHorseDonkey.java
-+++ b/src/main/java/net/minecraft/server/EntityHorseDonkey.java
-@@ -33,7 +33,7 @@ public class EntityHorseDonkey extends EntityHorseChestedAbstract {
- }
-
- public EntityAgeable createChild(EntityAgeable entityageable) {
-- Object object = entityageable instanceof EntityHorse ? new EntityHorseMule(this.world) : new EntityHorseDonkey(this.world);
-+ Object object = entityageable instanceof EntityHorse ? EntityTypes.MULE.create(world) : EntityTypes.DONKEY.create(world); // Paper
-
- this.a(entityageable, (EntityHorseAbstract) object);
- return (EntityAgeable) object;
-diff --git a/src/main/java/net/minecraft/server/EntityHorseSkeleton.java b/src/main/java/net/minecraft/server/EntityHorseSkeleton.java
-index eae2b26655..0a092acdfe 100644
---- a/src/main/java/net/minecraft/server/EntityHorseSkeleton.java
-+++ b/src/main/java/net/minecraft/server/EntityHorseSkeleton.java
-@@ -134,7 +134,7 @@ public class EntityHorseSkeleton extends EntityHorseAbstract {
-
- @Nullable
- public EntityAgeable createChild(EntityAgeable entityageable) {
-- return new EntityHorseSkeleton(this.world);
-+ return EntityTypes.SKELETON_HORSE.create(world); // Paper
- }
-
- public boolean a(EntityHuman entityhuman, EnumHand enumhand) {
-diff --git a/src/main/java/net/minecraft/server/EntityHorseZombie.java b/src/main/java/net/minecraft/server/EntityHorseZombie.java
-index c23bc72fc8..a1873f557c 100644
---- a/src/main/java/net/minecraft/server/EntityHorseZombie.java
-+++ b/src/main/java/net/minecraft/server/EntityHorseZombie.java
-@@ -41,7 +41,7 @@ public class EntityHorseZombie extends EntityHorseAbstract {
-
- @Nullable
- public EntityAgeable createChild(EntityAgeable entityageable) {
-- return new EntityHorseZombie(this.world);
-+ return EntityTypes.ZOMBIE_HORSE.create(world); // Paper
- }
-
- public boolean a(EntityHuman entityhuman, EnumHand enumhand) {
-diff --git a/src/main/java/net/minecraft/server/EntityLlama.java b/src/main/java/net/minecraft/server/EntityLlama.java
-index 5e19768710..82a32c61ed 100644
---- a/src/main/java/net/minecraft/server/EntityLlama.java
-+++ b/src/main/java/net/minecraft/server/EntityLlama.java
-@@ -285,7 +285,7 @@ public class EntityLlama extends EntityHorseChestedAbstract implements IRangedEn
- }
-
- public EntityLlama createChild(EntityAgeable entityageable) {
-- EntityLlama entityllama = new EntityLlama(this.world);
-+ EntityLlama entityllama = EntityTypes.LLAMA.create(world); // Paper
-
- this.a(entityageable, (EntityHorseAbstract) entityllama);
- EntityLlama entityllama1 = (EntityLlama) entityageable;
-diff --git a/src/main/java/net/minecraft/server/EntityMushroomCow.java b/src/main/java/net/minecraft/server/EntityMushroomCow.java
-index dde9f1e61e..638dbe978d 100644
---- a/src/main/java/net/minecraft/server/EntityMushroomCow.java
-+++ b/src/main/java/net/minecraft/server/EntityMushroomCow.java
-@@ -40,7 +40,7 @@ public class EntityMushroomCow extends EntityCow {
- this.world.addParticle(Particles.u, this.locX, this.locY + (double) (this.length / 2.0F), this.locZ, 0.0D, 0.0D, 0.0D);
- if (!this.world.isClientSide) {
- // this.die(); // CraftBukkit - moved down
-- EntityCow entitycow = new EntityCow(this.world);
-+ EntityCow entitycow = EntityTypes.COW.create(world); // Paper
-
- entitycow.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, this.pitch);
- entitycow.setHealth(this.getHealth());
-@@ -74,7 +74,7 @@ public class EntityMushroomCow extends EntityCow {
- }
-
- public EntityMushroomCow createChild(EntityAgeable entityageable) {
-- return new EntityMushroomCow(this.world);
-+ return EntityTypes.MOOSHROOM.create(world); // Paper
- }
-
- @Nullable
-diff --git a/src/main/java/net/minecraft/server/EntityOcelot.java b/src/main/java/net/minecraft/server/EntityOcelot.java
-index ba074c10c6..13c84bda84 100644
---- a/src/main/java/net/minecraft/server/EntityOcelot.java
-+++ b/src/main/java/net/minecraft/server/EntityOcelot.java
-@@ -154,7 +154,7 @@ public class EntityOcelot extends EntityTameableAnimal {
- }
-
- public EntityOcelot createChild(EntityAgeable entityageable) {
-- EntityOcelot entityocelot = new EntityOcelot(this.world);
-+ EntityOcelot entityocelot = EntityTypes.OCELOT.create(world); // Paper
-
- if (this.isTamed()) {
- entityocelot.setOwnerUUID(this.getOwnerUUID());
-@@ -237,7 +237,7 @@ public class EntityOcelot extends EntityTameableAnimal {
- groupdataentity = super.prepare(difficultydamagescaler, groupdataentity, nbttagcompound);
- if (spawnBonus && this.getCatType() == 0 && this.world.random.nextInt(7) == 0) { // Spigot
- for (int i = 0; i < 2; ++i) {
-- EntityOcelot entityocelot = new EntityOcelot(this.world);
-+ EntityOcelot entityocelot = EntityTypes.OCELOT.create(world); // Paper
-
- entityocelot.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, 0.0F);
- entityocelot.setAgeRaw(-24000);
-diff --git a/src/main/java/net/minecraft/server/EntityPig.java b/src/main/java/net/minecraft/server/EntityPig.java
-index 9dc2d8be27..d1689dc33a 100644
---- a/src/main/java/net/minecraft/server/EntityPig.java
-+++ b/src/main/java/net/minecraft/server/EntityPig.java
-@@ -153,7 +153,7 @@ public class EntityPig extends EntityAnimal {
-
- public void onLightningStrike(EntityLightning entitylightning) {
- if (!this.world.isClientSide && !this.dead) {
-- EntityPigZombie entitypigzombie = new EntityPigZombie(this.world);
-+ EntityPigZombie entitypigzombie = EntityTypes.ZOMBIE_PIGMAN.create(world); // Paper
-
- entitypigzombie.setSlot(EnumItemSlot.MAINHAND, new ItemStack(Items.GOLDEN_SWORD));
- entitypigzombie.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, this.pitch);
-@@ -242,7 +242,7 @@ public class EntityPig extends EntityAnimal {
- }
-
- public EntityPig createChild(EntityAgeable entityageable) {
-- return new EntityPig(this.world);
-+ return EntityTypes.PIG.create(world); // Paper
- }
-
- public boolean f(ItemStack itemstack) {
-diff --git a/src/main/java/net/minecraft/server/EntityPolarBear.java b/src/main/java/net/minecraft/server/EntityPolarBear.java
-index a02020d5fc..dbb534c9cd 100644
---- a/src/main/java/net/minecraft/server/EntityPolarBear.java
-+++ b/src/main/java/net/minecraft/server/EntityPolarBear.java
-@@ -18,7 +18,7 @@ public class EntityPolarBear extends EntityAnimal {
- }
-
- public EntityAgeable createChild(EntityAgeable entityageable) {
-- return new EntityPolarBear(this.world);
-+ return EntityTypes.POLAR_BEAR.create(world); // Paper
- }
-
- public boolean f(ItemStack itemstack) {
-diff --git a/src/main/java/net/minecraft/server/EntityRabbit.java b/src/main/java/net/minecraft/server/EntityRabbit.java
-index e545b1c9b3..d6bac06a7a 100644
---- a/src/main/java/net/minecraft/server/EntityRabbit.java
-+++ b/src/main/java/net/minecraft/server/EntityRabbit.java
-@@ -251,7 +251,7 @@ public class EntityRabbit extends EntityAnimal {
- }
-
- public EntityRabbit createChild(EntityAgeable entityageable) {
-- EntityRabbit entityrabbit = new EntityRabbit(this.world);
-+ EntityRabbit entityrabbit = EntityTypes.RABBIT.create(world); // Paper
- int i = this.dJ();
-
- if (this.random.nextInt(20) != 0) {
-diff --git a/src/main/java/net/minecraft/server/EntitySheep.java b/src/main/java/net/minecraft/server/EntitySheep.java
-index f7a25c1483..c35d1eef43 100644
---- a/src/main/java/net/minecraft/server/EntitySheep.java
-+++ b/src/main/java/net/minecraft/server/EntitySheep.java
-@@ -247,7 +247,7 @@ public class EntitySheep extends EntityAnimal {
-
- public EntitySheep createChild(EntityAgeable entityageable) {
- EntitySheep entitysheep = (EntitySheep) entityageable;
-- EntitySheep entitysheep1 = new EntitySheep(this.world);
-+ EntitySheep entitysheep1 = EntityTypes.SHEEP.create(world); // Paper
-
- entitysheep1.setColor(this.a((EntityAnimal) this, (EntityAnimal) entitysheep));
- return entitysheep1;
-diff --git a/src/main/java/net/minecraft/server/EntitySpider.java b/src/main/java/net/minecraft/server/EntitySpider.java
-index a42b8d554f..9ef1c9baf2 100644
---- a/src/main/java/net/minecraft/server/EntitySpider.java
-+++ b/src/main/java/net/minecraft/server/EntitySpider.java
-@@ -111,7 +111,7 @@ public class EntitySpider extends EntityMonster {
- Object object = super.prepare(difficultydamagescaler, groupdataentity, nbttagcompound);
-
- if (this.world.random.nextInt(100) == 0) {
-- EntitySkeleton entityskeleton = new EntitySkeleton(this.world);
-+ EntitySkeleton entityskeleton = EntityTypes.SKELETON.create(world); // Paper
-
- entityskeleton.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, 0.0F);
- entityskeleton.prepare(difficultydamagescaler, (GroupDataEntity) null, (NBTTagCompound) null);
-diff --git a/src/main/java/net/minecraft/server/EntityTurtle.java b/src/main/java/net/minecraft/server/EntityTurtle.java
-index a533e0eb5b..270b950820 100644
---- a/src/main/java/net/minecraft/server/EntityTurtle.java
-+++ b/src/main/java/net/minecraft/server/EntityTurtle.java
-@@ -218,7 +218,7 @@ public class EntityTurtle extends EntityAnimal {
-
- @Nullable
- public EntityAgeable createChild(EntityAgeable entityageable) {
-- return new EntityTurtle(this.world);
-+ return EntityTypes.TURTLE.create(world); // Paper
- }
-
- public boolean f(ItemStack itemstack) {
-diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java
-index d74bfa1201..24ca351194 100644
---- a/src/main/java/net/minecraft/server/EntityTypes.java
-+++ b/src/main/java/net/minecraft/server/EntityTypes.java
-@@ -269,6 +269,7 @@ public class EntityTypes<T extends Entity> {
- return this.aX;
- }
-
-+ @Nullable public T create(World world) { return a(world); } // Paper - OBFHELPER
- @Nullable
- public T a(World world) {
- return this.aT.apply(world); // CraftBukkit - decompile error
-diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java
-index f01e776fe5..40b3ffd8ca 100644
---- a/src/main/java/net/minecraft/server/EntityVillager.java
-+++ b/src/main/java/net/minecraft/server/EntityVillager.java
-@@ -592,7 +592,7 @@ public class EntityVillager extends EntityAgeable implements NPC, IMerchant {
- }
-
- public EntityVillager createChild(EntityAgeable entityageable) {
-- EntityVillager entityvillager = new EntityVillager(this.world);
-+ EntityVillager entityvillager = EntityTypes.VILLAGER.create(world); // Paper
-
- entityvillager.prepare(this.world.getDamageScaler(new BlockPosition(entityvillager)), (GroupDataEntity) null, (NBTTagCompound) null);
- return entityvillager;
-@@ -604,7 +604,7 @@ public class EntityVillager extends EntityAgeable implements NPC, IMerchant {
-
- public void onLightningStrike(EntityLightning entitylightning) {
- if (!this.world.isClientSide && !this.dead) {
-- EntityWitch entitywitch = new EntityWitch(this.world);
-+ EntityWitch entitywitch = EntityTypes.WITCH.create(world); // Paper
-
- // Paper start
- if (org.bukkit.craftbukkit.event.CraftEventFactory.callEntityZapEvent(this, entitylightning, entitywitch).isCancelled()) {
-diff --git a/src/main/java/net/minecraft/server/EntityWolf.java b/src/main/java/net/minecraft/server/EntityWolf.java
-index 4f1696d018..46d8e0a1f4 100644
---- a/src/main/java/net/minecraft/server/EntityWolf.java
-+++ b/src/main/java/net/minecraft/server/EntityWolf.java
-@@ -342,7 +342,7 @@ public class EntityWolf extends EntityTameableAnimal {
- }
-
- public EntityWolf createChild(EntityAgeable entityageable) {
-- EntityWolf entitywolf = new EntityWolf(this.world);
-+ EntityWolf entitywolf = EntityTypes.WOLF.create(world); // Paper
- UUID uuid = this.getOwnerUUID();
-
- if (uuid != null) {
-diff --git a/src/main/java/net/minecraft/server/EntityZombie.java b/src/main/java/net/minecraft/server/EntityZombie.java
-index 7998b80c17..81cc0c3b33 100644
---- a/src/main/java/net/minecraft/server/EntityZombie.java
-+++ b/src/main/java/net/minecraft/server/EntityZombie.java
-@@ -208,7 +208,7 @@ public class EntityZombie extends EntityMonster {
- }
-
- protected void dE() {
-- this.a((EntityZombie) (new EntityDrowned(this.world)));
-+ this.a((EntityZombie) EntityTypes.DROWNED.create(world)); // Paper
- this.world.a((EntityHuman) null, 1040, new BlockPosition((int) this.locX, (int) this.locY, (int) this.locZ), 0);
- }
-
-@@ -261,7 +261,7 @@ public class EntityZombie extends EntityMonster {
- int i = MathHelper.floor(this.locX);
- int j = MathHelper.floor(this.locY);
- int k = MathHelper.floor(this.locZ);
-- EntityZombie entityzombie = new EntityZombie(this.world);
-+ EntityZombie entityzombie = EntityTypes.ZOMBIE.create(world); // Paper
-
- for (int l = 0; l < 50; ++l) {
- int i1 = i + MathHelper.nextInt(this.random, 7, 40) * MathHelper.nextInt(this.random, -1, 1);
-@@ -385,7 +385,7 @@ public class EntityZombie extends EntityMonster {
- }
-
- EntityVillager entityvillager = (EntityVillager) entityliving;
-- EntityZombieVillager entityzombievillager = new EntityZombieVillager(this.world);
-+ EntityZombieVillager entityzombievillager = EntityTypes.ZOMBIE_VILLAGER.create(world); // Paper
-
- entityzombievillager.u(entityvillager);
- // this.world.kill(entityvillager); // CraftBukkit - moved down
-@@ -450,7 +450,7 @@ public class EntityZombie extends EntityMonster {
- this.startRiding(entitychicken);
- }
- } else if ((double) this.world.random.nextFloat() < 0.05D) {
-- EntityChicken entitychicken1 = new EntityChicken(this.world);
-+ EntityChicken entitychicken1 = EntityTypes.CHICKEN.create(world); // Paper
-
- entitychicken1.setPositionRotation(this.locX, this.locY, this.locZ, this.yaw, 0.0F);
- entitychicken1.prepare(difficultydamagescaler, (GroupDataEntity) null, (NBTTagCompound) null);
-diff --git a/src/main/java/net/minecraft/server/EntityZombieHusk.java b/src/main/java/net/minecraft/server/EntityZombieHusk.java
-index 85d402965b..0cca7b6d51 100644
---- a/src/main/java/net/minecraft/server/EntityZombieHusk.java
-+++ b/src/main/java/net/minecraft/server/EntityZombieHusk.java
-@@ -54,7 +54,7 @@ public class EntityZombieHusk extends EntityZombie {
- }
-
- protected void dE() {
-- this.a(new EntityZombie(this.world));
-+ this.a(EntityTypes.ZOMBIE.create(world)); // Paper
- this.world.a((EntityHuman) null, 1041, new BlockPosition((int) this.locX, (int) this.locY, (int) this.locZ), 0);
- }
-
-diff --git a/src/main/java/net/minecraft/server/EntityZombieVillager.java b/src/main/java/net/minecraft/server/EntityZombieVillager.java
-index 359ac8b88c..96a1b1d3f2 100644
---- a/src/main/java/net/minecraft/server/EntityZombieVillager.java
-+++ b/src/main/java/net/minecraft/server/EntityZombieVillager.java
-@@ -119,7 +119,7 @@ public class EntityZombieVillager extends EntityZombie {
- }
-
- protected void dJ() {
-- EntityVillager entityvillager = new EntityVillager(this.world);
-+ EntityVillager entityvillager = EntityTypes.VILLAGER.create(world); // Paper
-
- entityvillager.u(this);
- entityvillager.setProfession(this.getProfession());
-diff --git a/src/main/java/net/minecraft/server/ItemArmorStand.java b/src/main/java/net/minecraft/server/ItemArmorStand.java
-index 576b3c5650..4dd0e39ec3 100644
---- a/src/main/java/net/minecraft/server/ItemArmorStand.java
-+++ b/src/main/java/net/minecraft/server/ItemArmorStand.java
-@@ -34,7 +34,7 @@ public class ItemArmorStand extends Item {
- if (!world.isClientSide) {
- world.setAir(blockposition);
- world.setAir(blockposition1);
-- EntityArmorStand entityarmorstand = new EntityArmorStand(world, d0 + 0.5D, d1, d2 + 0.5D);
-+ EntityArmorStand entityarmorstand = EntityTypes.ARMOR_STAND.create(world); // Paper
- float f = (float) MathHelper.d((MathHelper.g(itemactioncontext.h() - 180.0F) + 22.5F) / 45.0F) * 45.0F;
-
- entityarmorstand.setPositionRotation(d0 + 0.5D, d1, d2 + 0.5D, f, 0.0F);
-diff --git a/src/main/java/net/minecraft/server/MobSpawnerPhantom.java b/src/main/java/net/minecraft/server/MobSpawnerPhantom.java
-index 5ddf66eef5..bb7e072ee1 100644
---- a/src/main/java/net/minecraft/server/MobSpawnerPhantom.java
-+++ b/src/main/java/net/minecraft/server/MobSpawnerPhantom.java
-@@ -59,7 +59,7 @@ public class MobSpawnerPhantom {
- continue;
- }
- // Paper end
-- EntityPhantom entityphantom = new EntityPhantom(world);
-+ EntityPhantom entityphantom = EntityTypes.PHANTOM.create(world); // Paper
- entityphantom.spawningEntity = entityhuman.uniqueID; // Paper
- entityphantom.setPositionRotation(blockposition1, 0.0F, 0.0F);
- groupdataentity = entityphantom.prepare(difficultydamagescaler, groupdataentity, (NBTTagCompound) null);
-diff --git a/src/main/java/net/minecraft/server/PathfinderGoalHorseTrap.java b/src/main/java/net/minecraft/server/PathfinderGoalHorseTrap.java
-index d4fdcbdfd6..887e4461f3 100644
---- a/src/main/java/net/minecraft/server/PathfinderGoalHorseTrap.java
-+++ b/src/main/java/net/minecraft/server/PathfinderGoalHorseTrap.java
-@@ -36,7 +36,7 @@ public class PathfinderGoalHorseTrap extends PathfinderGoal {
- }
-
- private EntityHorseAbstract a(DifficultyDamageScaler difficultydamagescaler) {
-- EntityHorseSkeleton entityhorseskeleton = new EntityHorseSkeleton(this.a.world);
-+ EntityHorseSkeleton entityhorseskeleton = EntityTypes.SKELETON_HORSE.create(a.world); // Paper
-
- entityhorseskeleton.prepare(difficultydamagescaler, (GroupDataEntity) null, (NBTTagCompound) null);
- entityhorseskeleton.setPosition(this.a.locX, this.a.locY, this.a.locZ);
-@@ -49,7 +49,7 @@ public class PathfinderGoalHorseTrap extends PathfinderGoal {
- }
-
- private EntitySkeleton a(DifficultyDamageScaler difficultydamagescaler, EntityHorseAbstract entityhorseabstract) {
-- EntitySkeleton entityskeleton = new EntitySkeleton(entityhorseabstract.world);
-+ EntitySkeleton entityskeleton = EntityTypes.SKELETON.create(entityhorseabstract.world); // Paper
-
- entityskeleton.prepare(difficultydamagescaler, (GroupDataEntity) null, (NBTTagCompound) null);
- entityskeleton.setPosition(entityhorseabstract.locX, entityhorseabstract.locY, entityhorseabstract.locZ);
-diff --git a/src/main/java/net/minecraft/server/VillageSiege.java b/src/main/java/net/minecraft/server/VillageSiege.java
-index 0ac1fb53a4..509d62f6b6 100644
---- a/src/main/java/net/minecraft/server/VillageSiege.java
-+++ b/src/main/java/net/minecraft/server/VillageSiege.java
-@@ -134,7 +134,7 @@ public class VillageSiege {
- EntityZombie entityzombie;
-
- try {
-- entityzombie = new EntityZombie(this.a);
-+ entityzombie = EntityTypes.ZOMBIE.create(this.a); // Paper
- entityzombie.prepare(this.a.getDamageScaler(new BlockPosition(entityzombie)), (GroupDataEntity) null, (NBTTagCompound) null);
- } catch (Exception exception) {
- exception.printStackTrace();
-diff --git a/src/main/java/net/minecraft/server/WorldGenEndCityPieces.java b/src/main/java/net/minecraft/server/WorldGenEndCityPieces.java
-index 94b21693e2..0a223cfe5a 100644
---- a/src/main/java/net/minecraft/server/WorldGenEndCityPieces.java
-+++ b/src/main/java/net/minecraft/server/WorldGenEndCityPieces.java
-@@ -270,7 +270,7 @@ public class WorldGenEndCityPieces {
- TileEntityLootable.a(generatoraccess, random, blockposition1, LootTables.c);
- }
- } else if (s.startsWith("Sentry")) {
-- EntityShulker entityshulker = new EntityShulker(generatoraccess.getMinecraftWorld());
-+ EntityShulker entityshulker = EntityTypes.SHULKER.create(generatoraccess.getMinecraftWorld()); // Paper
-
- entityshulker.setPosition((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D);
- entityshulker.g(blockposition);
-diff --git a/src/main/java/net/minecraft/server/WorldGenFeatureOceanRuinPieces.java b/src/main/java/net/minecraft/server/WorldGenFeatureOceanRuinPieces.java
-index 2def56b067..abeb4aa025 100644
---- a/src/main/java/net/minecraft/server/WorldGenFeatureOceanRuinPieces.java
-+++ b/src/main/java/net/minecraft/server/WorldGenFeatureOceanRuinPieces.java
-@@ -154,8 +154,7 @@ public class WorldGenFeatureOceanRuinPieces {
- ((TileEntityChest) tileentity).setLootTable(this.h ? LootTables.q : LootTables.p, random.nextLong());
- }
- } else if ("drowned".equals(s)) {
-- EntityDrowned entitydrowned = new EntityDrowned(generatoraccess.getMinecraftWorld());
--
-+ EntityDrowned entitydrowned = EntityTypes.DROWNED.create(generatoraccess.getMinecraftWorld()); // Paper
- entitydrowned.di();
- entitydrowned.setPositionRotation(blockposition, 0.0F, 0.0F);
- entitydrowned.prepare(generatoraccess.getDamageScaler(blockposition), (GroupDataEntity) null, (NBTTagCompound) null);
-diff --git a/src/main/java/net/minecraft/server/WorldGenMonumentPieces.java b/src/main/java/net/minecraft/server/WorldGenMonumentPieces.java
-index 0e7aed09d1..493a86e1bf 100644
---- a/src/main/java/net/minecraft/server/WorldGenMonumentPieces.java
-+++ b/src/main/java/net/minecraft/server/WorldGenMonumentPieces.java
-@@ -1800,7 +1800,7 @@ public class WorldGenMonumentPieces {
- protected static final IBlockData d = WorldGenMonumentPieces.WorldGenMonumentPiece.b;
- protected static final IBlockData e = Blocks.SEA_LANTERN.getBlockData();
- protected static final IBlockData f = Blocks.WATER.getBlockData();
-- protected static final Set<Block> g = ImmutableSet.builder().add(Blocks.ICE).add(Blocks.PACKED_ICE).add(Blocks.BLUE_ICE).add(WorldGenMonumentPieces.WorldGenMonumentPiece.f.getBlock()).build();
-+ protected static final Set<Block> g = ImmutableSet.<Block>builder().add(Blocks.ICE).add(Blocks.PACKED_ICE).add(Blocks.BLUE_ICE).add(WorldGenMonumentPieces.WorldGenMonumentPiece.f.getBlock()).build(); // Paper - decompile fix
- protected static final int h = b(2, 0, 0);
- protected static final int i = b(2, 2, 0);
- protected static final int j = b(0, 1, 0);
-@@ -1923,7 +1923,7 @@ public class WorldGenMonumentPieces {
- int j1 = this.b(i, k);
-
- if (structureboundingbox.b((BaseBlockPosition) (new BlockPosition(l, i1, j1)))) {
-- EntityGuardianElder entityguardianelder = new EntityGuardianElder(generatoraccess.getMinecraftWorld());
-+ EntityGuardianElder entityguardianelder = EntityTypes.ELDER_GUARDIAN.create(generatoraccess.getMinecraftWorld()); // Paper
-
- entityguardianelder.heal(entityguardianelder.getMaxHealth());
- entityguardianelder.setPositionRotation((double) l + 0.5D, (double) i1, (double) j1 + 0.5D, 0.0F, 0.0F);
-diff --git a/src/main/java/net/minecraft/server/WorldGenVillagePieces.java b/src/main/java/net/minecraft/server/WorldGenVillagePieces.java
-index 5fa2987d2a..967e33b3d7 100644
---- a/src/main/java/net/minecraft/server/WorldGenVillagePieces.java
-+++ b/src/main/java/net/minecraft/server/WorldGenVillagePieces.java
-@@ -1640,7 +1640,7 @@ public class WorldGenVillagePieces {
-
- ++this.a;
- if (this.h) {
-- EntityZombieVillager entityzombievillager = new EntityZombieVillager(generatoraccess.getMinecraftWorld());
-+ EntityZombieVillager entityzombievillager = EntityTypes.ZOMBIE_VILLAGER.create(generatoraccess.getMinecraftWorld()); // Paper
-
- entityzombievillager.setPositionRotation((double) j1 + 0.5D, (double) k1, (double) l1 + 0.5D, 0.0F, 0.0F);
- entityzombievillager.prepare(generatoraccess.getDamageScaler(new BlockPosition(entityzombievillager)), (GroupDataEntity) null, (NBTTagCompound) null);
-@@ -1648,7 +1648,7 @@ public class WorldGenVillagePieces {
- entityzombievillager.di();
- generatoraccess.addEntity(entityzombievillager, org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN); // CraftBukkit - add SpawnReason
- } else {
-- EntityVillager entityvillager = new EntityVillager(generatoraccess.getMinecraftWorld());
-+ EntityVillager entityvillager = EntityTypes.VILLAGER.create(generatoraccess.getMinecraftWorld()); // Paper
-
- entityvillager.setPositionRotation((double) j1 + 0.5D, (double) k1, (double) l1 + 0.5D, 0.0F, 0.0F);
- entityvillager.setProfession(this.c(i1, generatoraccess.m().nextInt(6)));
-diff --git a/src/main/java/net/minecraft/server/WorldGenWitchHut.java b/src/main/java/net/minecraft/server/WorldGenWitchHut.java
-index efb0379ce3..3d8193c477 100644
---- a/src/main/java/net/minecraft/server/WorldGenWitchHut.java
-+++ b/src/main/java/net/minecraft/server/WorldGenWitchHut.java
-@@ -81,7 +81,7 @@ public class WorldGenWitchHut extends WorldGenScatteredPiece {
-
- if (structureboundingbox.b((BaseBlockPosition) (new BlockPosition(j, i, k)))) {
- this.e = true;
-- EntityWitch entitywitch = new EntityWitch(generatoraccess.getMinecraftWorld());
-+ EntityWitch entitywitch = EntityTypes.WITCH.create(generatoraccess.getMinecraftWorld()); // Paper
-
- entitywitch.di();
- entitywitch.setPositionRotation((double) j + 0.5D, (double) i, (double) k + 0.5D, 0.0F, 0.0F);
-diff --git a/src/main/java/net/minecraft/server/WorldGenWoodlandMansionPieces.java b/src/main/java/net/minecraft/server/WorldGenWoodlandMansionPieces.java
-index 11010d8e12..4eb746ebb0 100644
---- a/src/main/java/net/minecraft/server/WorldGenWoodlandMansionPieces.java
-+++ b/src/main/java/net/minecraft/server/WorldGenWoodlandMansionPieces.java
-@@ -23,14 +23,14 @@ public class WorldGenWoodlandMansionPieces {
- static class h extends WorldGenWoodlandMansionPieces.f {
-
- private h() {
-- super(null);
-+ super(); // Paper - decompile fix
- }
- }
-
- static class f extends WorldGenWoodlandMansionPieces.b {
-
- private f() {
-- super(null);
-+ super(); // Paper - decompile fix
- }
-
- public String a(Random random) {
-@@ -65,7 +65,7 @@ public class WorldGenWoodlandMansionPieces {
- static class a extends WorldGenWoodlandMansionPieces.b {
-
- private a() {
-- super(null);
-+ super(); // Paper - decompile fix
- }
-
- public String a(Random random) {
-@@ -1065,15 +1065,13 @@ public class WorldGenWoodlandMansionPieces {
-
- this.a(generatoraccess, structureboundingbox, random, blockposition, LootTables.o, iblockdata);
- } else if ("Mage".equals(s)) {
-- EntityEvoker entityevoker = new EntityEvoker(generatoraccess.getMinecraftWorld());
--
-+ EntityEvoker entityevoker = EntityTypes.EVOKER.create(generatoraccess.getMinecraftWorld()); // Paper
- entityevoker.di();
- entityevoker.setPositionRotation(blockposition, 0.0F, 0.0F);
- generatoraccess.addEntity(entityevoker);
- generatoraccess.setTypeAndData(blockposition, Blocks.AIR.getBlockData(), 2);
- } else if ("Warrior".equals(s)) {
-- EntityVindicator entityvindicator = new EntityVindicator(generatoraccess.getMinecraftWorld());
--
-+ EntityVindicator entityvindicator = EntityTypes.VINDICATOR.create(generatoraccess.getMinecraftWorld()); // Paper
- entityvindicator.di();
- entityvindicator.setPositionRotation(blockposition, 0.0F, 0.0F);
- entityvindicator.prepare(generatoraccess.getDamageScaler(new BlockPosition(entityvindicator)), (GroupDataEntity) null, (NBTTagCompound) null);
-diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
-index 53e7834cca..5c2421ac38 100644
---- a/src/main/java/net/minecraft/server/WorldServer.java
-+++ b/src/main/java/net/minecraft/server/WorldServer.java
-@@ -495,7 +495,7 @@ public class WorldServer extends World implements IAsyncTaskHandler {
- boolean flag2 = this.getGameRules().getBoolean("doMobSpawning") && this.random.nextDouble() < (double) difficultydamagescaler.b() * paperConfig.skeleHorseSpawnChance; // Paper
-
- if (flag2) {
-- EntityHorseSkeleton entityhorseskeleton = new EntityHorseSkeleton(this);
-+ EntityHorseSkeleton entityhorseskeleton = EntityTypes.SKELETON_HORSE.create(this); // Paper
-
- entityhorseskeleton.s(true);
- entityhorseskeleton.setAgeRaw(0);
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 7c0a530533..40ee34675c 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -1181,153 +1181,153 @@ public class CraftWorld implements World {
- entity.setPositionRotation(x, y, z, 0, 0);
- } else if (LivingEntity.class.isAssignableFrom(clazz)) {
- if (Chicken.class.isAssignableFrom(clazz)) {
-- entity = new EntityChicken(world);
-+ entity = EntityTypes.CHICKEN.create(world); // Paper
- } else if (Cow.class.isAssignableFrom(clazz)) {
- if (MushroomCow.class.isAssignableFrom(clazz)) {
-- entity = new EntityMushroomCow(world);
-+ entity = EntityTypes.MOOSHROOM.create(world); // Paper
- } else {
-- entity = new EntityCow(world);
-+ entity = EntityTypes.COW.create(world); // Paper
- }
- } else if (Golem.class.isAssignableFrom(clazz)) {
- if (Snowman.class.isAssignableFrom(clazz)) {
-- entity = new EntitySnowman(world);
-+ entity = EntityTypes.SNOW_GOLEM.create(world); // Paper
- } else if (IronGolem.class.isAssignableFrom(clazz)) {
-- entity = new EntityIronGolem(world);
-+ entity = EntityTypes.IRON_GOLEM.create(world); // Paper
- } else if (Shulker.class.isAssignableFrom(clazz)) {
-- entity = new EntityShulker(world);
-+ entity = EntityTypes.SHULKER.create(world); // Paper
- }
- } else if (Creeper.class.isAssignableFrom(clazz)) {
-- entity = new EntityCreeper(world);
-+ entity = EntityTypes.CREEPER.create(world); // Paper
- } else if (Ghast.class.isAssignableFrom(clazz)) {
-- entity = new EntityGhast(world);
-+ entity = EntityTypes.GHAST.create(world); // Paper
- } else if (Pig.class.isAssignableFrom(clazz)) {
-- entity = new EntityPig(world);
-+ entity = EntityTypes.PIG.create(world); // Paper
- } else if (Player.class.isAssignableFrom(clazz)) {
- // need a net server handler for this one
- } else if (Sheep.class.isAssignableFrom(clazz)) {
-- entity = new EntitySheep(world);
-+ entity = EntityTypes.SHEEP.create(world); // Paper
- } else if (AbstractHorse.class.isAssignableFrom(clazz)) {
- if (ChestedHorse.class.isAssignableFrom(clazz)) {
- if (Donkey.class.isAssignableFrom(clazz)) {
-- entity = new EntityHorseDonkey(world);
-+ entity = EntityTypes.DONKEY.create(world); // Paper
- } else if (Mule.class.isAssignableFrom(clazz)) {
-- entity = new EntityHorseMule(world);
-+ entity = EntityTypes.MULE.create(world); // Paper
- } else if (Llama.class.isAssignableFrom(clazz)) {
-- entity = new EntityLlama(world);
-+ entity = EntityTypes.LLAMA.create(world); // Paper
- }
- } else if (SkeletonHorse.class.isAssignableFrom(clazz)) {
-- entity = new EntityHorseSkeleton(world);
-+ entity = EntityTypes.SKELETON_HORSE.create(world); // Paper
- } else if (ZombieHorse.class.isAssignableFrom(clazz)) {
-- entity = new EntityHorseZombie(world);
-+ entity = EntityTypes.ZOMBIE_HORSE.create(world); // Paper
- } else {
-- entity = new EntityHorse(world);
-+ entity = EntityTypes.HORSE.create(world); // Paper
- }
- } else if (Skeleton.class.isAssignableFrom(clazz)) {
- if (Stray.class.isAssignableFrom(clazz)){
-- entity = new EntitySkeletonStray(world);
-+ entity = EntityTypes.STRAY.create(world); // Paper
- } else if (WitherSkeleton.class.isAssignableFrom(clazz)) {
-- entity = new EntitySkeletonWither(world);
-+ entity = EntityTypes.WITHER_SKELETON.create(world); // Paper
- } else {
-- entity = new EntitySkeleton(world);
-+ entity = EntityTypes.SKELETON.create(world); // Paper
- }
- } else if (Slime.class.isAssignableFrom(clazz)) {
- if (MagmaCube.class.isAssignableFrom(clazz)) {
-- entity = new EntityMagmaCube(world);
-+ entity = EntityTypes.MAGMA_CUBE.create(world); // Paper
- } else {
-- entity = new EntitySlime(world);
-+ entity = EntityTypes.SLIME.create(world); // Paper
- }
- } else if (Spider.class.isAssignableFrom(clazz)) {
- if (CaveSpider.class.isAssignableFrom(clazz)) {
-- entity = new EntityCaveSpider(world);
-+ entity = EntityTypes.CAVE_SPIDER.create(world); // Paper
- } else {
-- entity = new EntitySpider(world);
-+ entity = EntityTypes.SPIDER.create(world); // Paper
- }
- } else if (Squid.class.isAssignableFrom(clazz)) {
-- entity = new EntitySquid(world);
-+ entity = EntityTypes.SQUID.create(world); // Paper
- } else if (Tameable.class.isAssignableFrom(clazz)) {
- if (Wolf.class.isAssignableFrom(clazz)) {
-- entity = new EntityWolf(world);
-+ entity = EntityTypes.WOLF.create(world); // Paper
- } else if (Ocelot.class.isAssignableFrom(clazz)) {
-- entity = new EntityOcelot(world);
-+ entity = EntityTypes.OCELOT.create(world); // Paper
- } else if (Parrot.class.isAssignableFrom(clazz)) {
-- entity = new EntityParrot(world);
-+ entity = EntityTypes.PARROT.create(world); // Paper
- }
- } else if (PigZombie.class.isAssignableFrom(clazz)) {
-- entity = new EntityPigZombie(world);
-+ entity = EntityTypes.ZOMBIE_PIGMAN.create(world); // Paper
- } else if (Zombie.class.isAssignableFrom(clazz)) {
- if (Husk.class.isAssignableFrom(clazz)) {
-- entity = new EntityZombieHusk(world);
-+ entity = EntityTypes.HUSK.create(world); // Paper
- } else if (ZombieVillager.class.isAssignableFrom(clazz)) {
-- entity = new EntityZombieVillager(world);
-+ entity = EntityTypes.ZOMBIE_VILLAGER.create(world); // Paper
- } else if (Drowned.class.isAssignableFrom(clazz)) {
-- entity = new EntityDrowned(world);
-+ entity = EntityTypes.DROWNED.create(world); // Paper
- } else {
-- entity = new EntityZombie(world);
-+ entity = EntityTypes.ZOMBIE.create(world); // Paper
- }
- } else if (Giant.class.isAssignableFrom(clazz)) {
-- entity = new EntityGiantZombie(world);
-+ entity = EntityTypes.GIANT.create(world); // Paper
- } else if (Silverfish.class.isAssignableFrom(clazz)) {
-- entity = new EntitySilverfish(world);
-+ entity = EntityTypes.SILVERFISH.create(world); // Paper
- } else if (Enderman.class.isAssignableFrom(clazz)) {
-- entity = new EntityEnderman(world);
-+ entity = EntityTypes.ENDERMAN.create(world); // Paper
- } else if (Blaze.class.isAssignableFrom(clazz)) {
-- entity = new EntityBlaze(world);
-+ entity = EntityTypes.BLAZE.create(world); // Paper
- } else if (Villager.class.isAssignableFrom(clazz)) {
-- entity = new EntityVillager(world);
-+ entity = EntityTypes.VILLAGER.create(world); // Paper
- } else if (Witch.class.isAssignableFrom(clazz)) {
-- entity = new EntityWitch(world);
-+ entity = EntityTypes.WITCH.create(world); // Paper
- } else if (Wither.class.isAssignableFrom(clazz)) {
-- entity = new EntityWither(world);
-+ entity = EntityTypes.WITHER.create(world); // Paper
- } else if (ComplexLivingEntity.class.isAssignableFrom(clazz)) {
- if (EnderDragon.class.isAssignableFrom(clazz)) {
-- entity = new EntityEnderDragon(world);
-+ entity = EntityTypes.ENDER_DRAGON.create(world); // Paper
- }
- } else if (Ambient.class.isAssignableFrom(clazz)) {
- if (Bat.class.isAssignableFrom(clazz)) {
-- entity = new EntityBat(world);
-+ entity = EntityTypes.BAT.create(world); // Paper
- }
- } else if (Rabbit.class.isAssignableFrom(clazz)) {
-- entity = new EntityRabbit(world);
-+ entity = EntityTypes.RABBIT.create(world); // Paper
- } else if (Endermite.class.isAssignableFrom(clazz)) {
-- entity = new EntityEndermite(world);
-+ entity = EntityTypes.ENDERMITE.create(world); // Paper
- } else if (Guardian.class.isAssignableFrom(clazz)) {
- if (ElderGuardian.class.isAssignableFrom(clazz)){
-- entity = new EntityGuardianElder(world);
-+ entity = EntityTypes.ELDER_GUARDIAN.create(world); // Paper
- } else {
-- entity = new EntityGuardian(world);
-+ entity = EntityTypes.GUARDIAN.create(world); // Paper
- }
- } else if (ArmorStand.class.isAssignableFrom(clazz)) {
-- entity = new EntityArmorStand(world, x, y, z);
-+ entity = EntityTypes.ARMOR_STAND.create(world); // Paper
- } else if (PolarBear.class.isAssignableFrom(clazz)) {
-- entity = new EntityPolarBear(world);
-+ entity = EntityTypes.POLAR_BEAR.create(world); // Paper
- } else if (Vex.class.isAssignableFrom(clazz)) {
-- entity = new EntityVex(world);
-+ entity = EntityTypes.VEX.create(world); // Paper
- } else if (Illager.class.isAssignableFrom(clazz)) {
- if (Spellcaster.class.isAssignableFrom(clazz)) {
- if (Evoker.class.isAssignableFrom(clazz)) {
-- entity = new EntityEvoker(world);
-+ entity = EntityTypes.EVOKER.create(world); // Paper
- } else if (Illusioner.class.isAssignableFrom(clazz)) {
-- entity = new EntityIllagerIllusioner(world);
-+ entity = EntityTypes.ILLUSIONER.create(world); // Paper
- }
- } else if (Vindicator.class.isAssignableFrom(clazz)) {
-- entity = new EntityVindicator(world);
-+ entity = EntityTypes.VINDICATOR.create(world); // Paper
- }
- } else if (Turtle.class.isAssignableFrom(clazz)) {
-- entity = new EntityTurtle(world);
-+ entity = EntityTypes.TURTLE.create(world); // Paper
- } else if (Phantom.class.isAssignableFrom(clazz)) {
-- entity = new EntityPhantom(world);
-+ entity = EntityTypes.PHANTOM.create(world); // Paper
- } else if (Fish.class.isAssignableFrom(clazz)) {
- if (Cod.class.isAssignableFrom(clazz)) {
-- entity = new EntityCod(world);
-+ entity = EntityTypes.COD.create(world); // Paper
- } else if (PufferFish.class.isAssignableFrom(clazz)) {
-- entity = new EntityPufferFish(world);
-+ entity = EntityTypes.PUFFERFISH.create(world); // Paper
- } else if (Salmon.class.isAssignableFrom(clazz)) {
-- entity = new EntitySalmon(world);
-+ entity = EntityTypes.SALMON.create(world); // Paper
- } else if (TropicalFish.class.isAssignableFrom(clazz)) {
-- entity = new EntityTropicalFish(world);
-+ entity = EntityTypes.TROPICAL_FISH.create(world); // Paper
- }
- } else if (Dolphin.class.isAssignableFrom(clazz)) {
-- entity = new EntityDolphin(world);
-+ entity = EntityTypes.DOLPHIN.create(world); // Paper
- }
-
- if (entity != null) {
---
-2.21.0
-
diff --git a/patches/removed/1.14/0400-limit-the-range-at-which-we-ll-consider-an-attackabl.patch b/patches/removed/1.14/0400-limit-the-range-at-which-we-ll-consider-an-attackabl.patch
deleted file mode 100644
index e95bd3c8bb..0000000000
--- a/patches/removed/1.14/0400-limit-the-range-at-which-we-ll-consider-an-attackabl.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-From a55362012ae6439bd007bf980dc0e094b7e9257a Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Tue, 13 Nov 2018 14:01:00 +0000
-Subject: [PATCH] limit the range at which we'll consider an attackable target
-
-This patch aims to ensure that MCP World#getNearestAttackablePlayer
-will not trigger chunk loads due to PathfinderGoalNearestAttackableTarget
-performing a ray trace operation by pre-checking the maximum limit;
-
-Given that the implementation shows that the limit should only ever
-decrease when set, allowing us to skip further checks earlier on
-when looking for an attackable entity
-
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index 645af17a58..7721dfee65 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -2721,8 +2721,13 @@ public abstract class World implements IEntityAccess, GeneratorAccess, IIBlockAc
- for (int i = 0; i < this.players.size(); ++i) {
- EntityHuman entityhuman1 = (EntityHuman) this.players.get(i);
-
-+ // Paper start
-+ // move distance check up, if set, check distance^2 is less than XZlimit^2, continue
-+ // 4th method param is XZlimit (at least at the time of commit)
-+ double d6 = entityhuman1.d(d0, entityhuman1.locY, d2);
-+ if (d3 < 0.0D || d6 < d3 * d3)
- if (!entityhuman1.abilities.isInvulnerable && entityhuman1.isAlive() && !entityhuman1.isSpectator() && (predicate == null || predicate.test(entityhuman1))) {
-- double d6 = entityhuman1.d(d0, entityhuman1.locY, d2);
-+ // Paper end
- double d7 = d3;
-
- if (entityhuman1.isSneaking()) {
---
-2.21.0
-
diff --git a/patches/removed/1.15/0078-Reduce-IO-ops-opening-a-new-region-file.patch b/patches/removed/1.15/0078-Reduce-IO-ops-opening-a-new-region-file.patch
deleted file mode 100644
index 2a972ea836..0000000000
--- a/patches/removed/1.15/0078-Reduce-IO-ops-opening-a-new-region-file.patch
+++ /dev/null
@@ -1,52 +0,0 @@
-From 299ea42df7d2ad51015e65dbbf5bb77a3d4f4e09 Mon Sep 17 00:00:00 2001
-From: Antony Riley <[email protected]>
-Date: Tue, 29 Mar 2016 06:56:23 +0300
-Subject: [PATCH] Reduce IO ops opening a new region file.
-
-
-diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java
-index fb529eac9..faf425588 100644
---- a/src/main/java/net/minecraft/server/RegionFile.java
-+++ b/src/main/java/net/minecraft/server/RegionFile.java
-@@ -26,7 +26,7 @@ public class RegionFile implements AutoCloseable {
- private final File file;
- // Spigot end
- private static final byte[] a = new byte[4096];
-- private final RandomAccessFile b; // PAIL dataFile
-+ private final RandomAccessFile b; private RandomAccessFile getDataFile() { return this.b; } // Paper - OBFHELPER // PAIL dataFile
- private final int[] c = new int[1024];
- private final int[] d = new int[1024];
- private final List<Boolean> e; // PAIL freeSectors
-@@ -59,10 +59,19 @@ public class RegionFile implements AutoCloseable {
- this.e.set(1, false);
- this.b.seek(0L);
-
-+ // Paper start
-+ java.nio.ByteBuffer header = java.nio.ByteBuffer.allocate(8192);
-+ while (header.hasRemaining()) {
-+ if (this.getDataFile().getChannel().read(header) == -1) throw new java.io.EOFException();
-+ }
-+ ((java.nio.Buffer) header).clear();
-+ java.nio.IntBuffer headerAsInts = header.asIntBuffer();
-+ // Paper end
-+
- int k;
-
- for (j = 0; j < 1024; ++j) {
-- k = this.b.readInt();
-+ k = headerAsInts.get(); // Paper
- this.c[j] = k;
- // Spigot start
- int length = k & 255;
-@@ -88,7 +97,7 @@ public class RegionFile implements AutoCloseable {
- }
-
- for (j = 0; j < 1024; ++j) {
-- k = this.b.readInt();
-+ k = headerAsInts.get(); // Paper
- this.d[j] = k;
- }
-
---
-2.22.0
-
diff --git a/patches/removed/1.15/0081-Do-not-load-chunks-for-light-checks.patch b/patches/removed/1.15/0081-Do-not-load-chunks-for-light-checks.patch
deleted file mode 100644
index 0e35a24e0a..0000000000
--- a/patches/removed/1.15/0081-Do-not-load-chunks-for-light-checks.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 317532f24f529ff717533f65beadc821a7a62bc7 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Thu, 31 Mar 2016 19:17:58 -0400
-Subject: [PATCH] Do not load chunks for light checks
-
-Should only happen for blocks on the edge that uses neighbors light level
-(certain blocks). In that case, there will be 3-4 other neighbors to get a light level from.
-
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index 1ffa8b42b..35fb686d8 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -592,6 +592,7 @@ public abstract class World implements IIBlockAccess, GeneratorAccess, AutoClose
- if (blockposition.getY() >= 256) {
- blockposition = new BlockPosition(blockposition.getX(), 255, blockposition.getZ());
- }
-+ if (!this.isLoaded(blockposition)) return 0; // Paper
-
- return this.getChunkAtWorldCoords(blockposition).a(blockposition, i);
- }
---
-2.22.0
-
diff --git a/patches/removed/1.15/0269-Provide-option-to-use-a-versioned-world-folder-for-t.patch b/patches/removed/1.15/0269-Provide-option-to-use-a-versioned-world-folder-for-t.patch
deleted file mode 100644
index 53e5cbaa46..0000000000
--- a/patches/removed/1.15/0269-Provide-option-to-use-a-versioned-world-folder-for-t.patch
+++ /dev/null
@@ -1,230 +0,0 @@
-From 9c38be60e37133286ee35635cdc44ac7a4eb555e Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sun, 29 Jul 2018 15:48:50 -0400
-Subject: [PATCH] Provide option to use a versioned world folder for testing
-
-This should not ever be used in production!!
-
-This setting is intended for testing so you can try out converting your world
-without actually modifying the world files.
-
-This will add some additional overhead to your world, but you're
-just testing anyways so that's not a big deal :)
-
-Will store in a folder named after the current version.
-
-PlayerData and Data folders are copied on server start, so there
-may be some delay there, but region files are only copied on demand.
-
-This is highly experiemental so backup your world before relying on this to not modify it
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-index eeef7d330b..dfdc7c384d 100644
---- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-@@ -13,6 +13,7 @@ import java.util.List;
- import java.util.Map;
- import java.util.concurrent.TimeUnit;
- import java.util.logging.Level;
-+import java.util.logging.Logger;
- import java.util.regex.Pattern;
-
- import com.google.common.collect.Lists;
-@@ -287,4 +288,27 @@ public class PaperConfig {
- Bukkit.getLogger().log(Level.INFO, "Using Aikar's Alternative Luck Formula to apply Luck attribute to all loot pool calculations. See https://luckformula.emc.gs");
- }
- }
-+
-+ public static boolean useVersionedWorld = false;
-+ private static void useVersionedWorld() {
-+ useVersionedWorld = getBoolean("settings.use-versioned-world", false);
-+ if (useVersionedWorld) {
-+ Logger logger = Bukkit.getLogger();
-+ String ver = MinecraftServer.getServer().getVersion();
-+ logger.log(Level.INFO, "******************************************************");
-+ logger.log(Level.INFO, "*** Using a versioned world folder. Your world will be saved");
-+ logger.log(Level.INFO, "*** to into the " + ver + " folder, but copied from your current world.");
-+ logger.log(Level.INFO, "*** ");
-+ logger.log(Level.INFO, "*** This setting should not be used in your real world!!!");
-+ logger.log(Level.INFO, "*** If you want to retain the new world, you need to move ");
-+ logger.log(Level.INFO, "*** the folders out of the " + ver + " folder and overwrite existing");
-+ logger.log(Level.INFO, "*** ");
-+ logger.log(Level.INFO, "*** Deleting the " + ver + " folder will cause it to recreate again");
-+ logger.log(Level.INFO, "*** from your unversioned world files.");
-+ logger.log(Level.INFO, "*** ");
-+ logger.log(Level.INFO, "*** You should backup your original world files incase something goes");
-+ logger.log(Level.INFO, "*** wrong with this system! This is not a backup system.");
-+ logger.log(Level.INFO, "******************************************************");
-+ }
-+ }
- }
-diff --git a/src/main/java/net/minecraft/server/RegionFileCache.java b/src/main/java/net/minecraft/server/RegionFileCache.java
-index 21b3b06f53..8718811655 100644
---- a/src/main/java/net/minecraft/server/RegionFileCache.java
-+++ b/src/main/java/net/minecraft/server/RegionFileCache.java
-@@ -10,13 +10,41 @@ import java.io.IOException;
- import javax.annotation.Nullable;
- import com.destroystokyo.paper.PaperConfig; // Paper
-
-+import org.apache.logging.log4j.LogManager;
-+
- public abstract class RegionFileCache implements AutoCloseable {
-
- public final Long2ObjectLinkedOpenHashMap<RegionFile> cache = new Long2ObjectLinkedOpenHashMap();
- private final File a;
-+ // Paper start
-+ private final File templateWorld;
-+ private final File actualWorld;
-+ private boolean useAltWorld;
-+ // Paper end
-+
-
- protected RegionFileCache(File file) {
- this.a = file;
-+ // Paper end
-+
-+ this.actualWorld = file;
-+ if (com.destroystokyo.paper.PaperConfig.useVersionedWorld) {
-+ this.useAltWorld = true;
-+ String name = file.getName();
-+ File container = file.getParentFile().getParentFile();
-+ if (name.equals("DIM-1") || name.equals("DIM1")) {
-+ container = container.getParentFile();
-+ }
-+ this.templateWorld = new File(container, name);
-+ File region = new File(file, "region");
-+ if (!region.exists()) {
-+ region.mkdirs();
-+ }
-+ } else {
-+ this.useAltWorld = false;
-+ this.templateWorld = file;
-+ }
-+ // Paper start
- }
-
- private RegionFile a(ChunkCoordIntPair chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit
-@@ -34,6 +62,7 @@ public abstract class RegionFileCache implements AutoCloseable {
- this.a.mkdirs();
- }
-
-+ copyIfNeeded(chunkcoordintpair.x, chunkcoordintpair.z); // Paper
- File file = new File(this.a, "r." + chunkcoordintpair.getRegionX() + "." + chunkcoordintpair.getRegionZ() + ".mca");
- if (existingOnly && !file.exists()) return null; // CraftBukkit
- RegionFile regionfile1 = new RegionFile(file);
-@@ -43,6 +72,15 @@ public abstract class RegionFileCache implements AutoCloseable {
- }
- }
-
-+ public static File getRegionFileName(File file, int i, int j) {
-+ File file1 = new File(file, "region");
-+ return new File(file1, "r." + (i >> 5) + "." + (j >> 5) + ".mca");
-+ }
-+ public synchronized boolean hasRegionFile(File file, int i, int j) {
-+ return cache.containsKey(ChunkCoordIntPair.pair(i, j));
-+ }
-+ // Paper end
-+
- @Nullable
- public NBTTagCompound read(ChunkCoordIntPair chunkcoordintpair) throws IOException {
- RegionFile regionfile = this.a(chunkcoordintpair, false); // CraftBukkit
-@@ -132,9 +170,33 @@ public abstract class RegionFileCache implements AutoCloseable {
-
- // CraftBukkit start
- public boolean chunkExists(ChunkCoordIntPair pos) throws IOException {
-+ copyIfNeeded(pos.x, pos.z); // Paper
- RegionFile regionfile = a(pos, true);
-
- return regionfile != null ? regionfile.d(pos) : false;
- }
- // CraftBukkit end
-+
-+ private void copyIfNeeded(int x, int z) {
-+ if (!useAltWorld) {
-+ return;
-+ }
-+ synchronized (RegionFileCache.class) {
-+ if (hasRegionFile(this.actualWorld, x, z)) {
-+ return;
-+ }
-+ File actual = RegionFileCache.getRegionFileName(this.actualWorld, x, z);
-+ File template = RegionFileCache.getRegionFileName(this.templateWorld, x, z);
-+ if (!actual.exists() && template.exists()) {
-+ try {
-+ net.minecraft.server.MinecraftServer.LOGGER.info("Copying" + template + " to " + actual);
-+ java.nio.file.Files.copy(template.toPath(), actual.toPath(), java.nio.file.StandardCopyOption.COPY_ATTRIBUTES);
-+ } catch (IOException e1) {
-+ LogManager.getLogger().error("Error copying " + template + " to " + actual, e1);
-+ MinecraftServer.getServer().safeShutdown(false);
-+ com.destroystokyo.paper.util.SneakyThrow.sneaky(e1);
-+ }
-+ }
-+ }
-+ }
- }
-diff --git a/src/main/java/net/minecraft/server/WorldNBTStorage.java b/src/main/java/net/minecraft/server/WorldNBTStorage.java
-index 350ac42d6b..eaae446861 100644
---- a/src/main/java/net/minecraft/server/WorldNBTStorage.java
-+++ b/src/main/java/net/minecraft/server/WorldNBTStorage.java
-@@ -31,6 +31,58 @@ public class WorldNBTStorage implements IPlayerFileData {
-
- public WorldNBTStorage(File file, String s, @Nullable MinecraftServer minecraftserver, DataFixer datafixer) {
- this.a = datafixer;
-+ // Paper start
-+ if (com.destroystokyo.paper.PaperConfig.useVersionedWorld) {
-+ File origBaseDir = new File(file, s);
-+ final String currentVersion = MinecraftServer.getServer().getVersion();
-+ file = new File(file, currentVersion);
-+ File baseDir = new File(file, s);
-+
-+ if (!baseDir.exists() && origBaseDir.exists() && !baseDir.mkdirs()) {
-+ LogManager.getLogger().error("Could not create world directory for " + file);
-+ System.exit(1);
-+ }
-+
-+ try {
-+ boolean printedHeader = false;
-+ String[] dirs = {"advancements", "data", "datapacks", "playerdata", "stats"};
-+ for (String dir : dirs) {
-+ File origPlayerData = new File(origBaseDir, dir);
-+ File targetPlayerData = new File(baseDir, dir);
-+ if (origPlayerData.exists() && !targetPlayerData.exists()) {
-+ if (!printedHeader) {
-+ LogManager.getLogger().info("**** VERSIONED WORLD - Copying files");
-+ printedHeader = true;
-+ }
-+ LogManager.getLogger().info("- Copying: " + dir);
-+ org.apache.commons.io.FileUtils.copyDirectory(origPlayerData, targetPlayerData);
-+ }
-+ }
-+
-+ String[] files = {"level.dat", "level.dat_old", "session.lock", "uid.dat"};
-+ for (String fileName : files) {
-+ File origPlayerData = new File(origBaseDir, fileName);
-+ File targetPlayerData = new File(baseDir, fileName);
-+ if (origPlayerData.exists() && !targetPlayerData.exists()) {
-+ if (!printedHeader) {
-+ LogManager.getLogger().info("- Copying files");
-+ printedHeader = true;
-+ }
-+ LogManager.getLogger().info("- Copying: " + fileName);
-+ org.apache.commons.io.FileUtils.copyFile(origPlayerData, targetPlayerData);
-+
-+ }
-+ }
-+ if (printedHeader) {
-+ LogManager.getLogger().info("**** VERSIONED WORLD - Copying DONE");
-+ }
-+ } catch (IOException e) {
-+ LogManager.getLogger().error("Error copying versioned world data for " + origBaseDir + " to " + baseDir, e);
-+ com.destroystokyo.paper.util.SneakyThrow.sneaky(e);
-+ }
-+
-+ }
-+ // Paper end
- this.baseDir = new File(file, s);
- this.baseDir.mkdirs();
- this.playerDir = new File(this.baseDir, "playerdata");
---
-2.22.0
-
diff --git a/patches/removed/1.15/0280-Detect-and-repair-corrupt-Region-Files.patch b/patches/removed/1.15/0280-Detect-and-repair-corrupt-Region-Files.patch
deleted file mode 100644
index 5cbc76867a..0000000000
--- a/patches/removed/1.15/0280-Detect-and-repair-corrupt-Region-Files.patch
+++ /dev/null
@@ -1,115 +0,0 @@
-From a1683b33a2ec42ca595cd7182ffdf5b376c844be Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 11 Aug 2018 00:49:20 -0400
-Subject: [PATCH] Detect and repair corrupt Region Files
-
-If the file has partial data written but not the full 8192 bytes,
-then the server will be unable to load that region file...
-
-I don't know why mojang only checks for 4096, when anything less than 8192 is a crash.
-
-But to be safe, it will attempt to back up the file.
-
-diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java
-index 3aeac69c26..17648c1c04 100644
---- a/src/main/java/net/minecraft/server/RegionFile.java
-+++ b/src/main/java/net/minecraft/server/RegionFile.java
-@@ -27,13 +27,13 @@ public class RegionFile implements AutoCloseable {
- // Spigot end
- private static final byte[] a = new byte[4096];
- private final RandomAccessFile b; private RandomAccessFile getDataFile() { return this.b; } // Paper - OBFHELPER // PAIL dataFile
-- private final int[] c = new int[1024];
-- private final int[] d = new int[1024];
-+ private final int[] c = new int[1024]; private final int[] offsets = c; // Paper - OBFHELPER
-+ private final int[] d = new int[1024]; private final int[] timestamps = d; // Paper - OBFHELPER
- private final List<Boolean> e; // PAIL freeSectors
-
- public RegionFile(File file) throws IOException {
- this.b = new RandomAccessFile(file, "rw");
-- if (this.b.length() < 4096L) {
-+ if (this.b.length() < 8192L) { // Paper - headers should be 8192
- this.b.write(RegionFile.a);
- this.b.write(RegionFile.a);
- }
-@@ -83,7 +83,7 @@ public class RegionFile implements AutoCloseable {
- this.b.seek(j * 4 + 4); // Go back to where we were
- }
- }
-- if (k != 0 && (k >> 8) + (length) <= this.e.size()) {
-+ if (k > 0 && (k >> 8) > 1 && (k >> 8) + (k & 255) <= this.e.size()) { // Paper >= 1 as 0/1 are the headers, and negative isnt valid
- for (int l = 0; l < (length); ++l) {
- // Spigot end
- this.e.set((k >> 8) + l, false);
-@@ -92,13 +92,14 @@ public class RegionFile implements AutoCloseable {
- // Spigot start
- else if (length > 0) {
- org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.WARNING, "Invalid chunk: ({0}, {1}) Offset: {2} Length: {3} runs off end file. {4}", new Object[]{j % 32, (int) (j / 32), k >> 8, length, file});
-+ deleteChunk(j); // Paper
- }
- // Spigot end
- }
-
- for (j = 0; j < 1024; ++j) {
- k = headerAsInts.get(); // Paper
-- this.d[j] = k;
-+ if (this.offsets[j] != 0) this.timestamps[j] = k; // Paper - don't set timestamp if it got 0'd above due to corruption
- }
-
- this.file = file; // Spigot
-@@ -349,6 +350,53 @@ public class RegionFile implements AutoCloseable {
- }
- // Spigot end
-
-+ // Paper start
-+ public synchronized void deleteChunk(int j1) {
-+ backup();
-+ int k = offsets[j1];
-+ int x = j1 & 1024;
-+ int z = j1 >> 2;
-+ int offset = (k >> 8);
-+ int len = (k & 255);
-+ String debug = "idx:" + + j1 + " - " + x + "," + z + " - offset: " + offset + " - len: " + len;
-+ try {
-+ timestamps[j1] = 0;
-+ offsets[j1] = 0;
-+ RandomAccessFile file = getDataFile();
-+ file.seek(j1 * 4);
-+ file.writeInt(0);
-+ // clear the timestamp
-+ file.seek(4096 + j1 * 4);
-+ file.writeInt(0);
-+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Deleted corrupt chunk (" + debug + ") " + this.file.getAbsolutePath(), e);
-+ } catch (IOException e) {
-+
-+ org.bukkit.Bukkit.getLogger().log(java.util.logging.Level.SEVERE, "Error deleting corrupt chunk (" + debug + ") " + this.file.getAbsolutePath(), e);
-+ }
-+ }
-+ private boolean backedUp = false;
-+ private synchronized void backup() {
-+ if (backedUp) {
-+ return;
-+ }
-+ backedUp = true;
-+ java.text.DateFormat formatter = new java.text.SimpleDateFormat("yyyy-MM-dd");
-+ java.util.Date today = new java.util.Date();
-+ File corrupt = new File(file.getParentFile(), file.getName() + "." + formatter.format(today) + ".corrupt");
-+ if (corrupt.exists()) {
-+ return;
-+ }
-+ org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger();
-+ logger.error("Region file " + file.getAbsolutePath() + " was corrupt. Backing up to " + corrupt.getAbsolutePath() + " and repairing");
-+ try {
-+ java.nio.file.Files.copy(file.toPath(), corrupt.toPath());
-+
-+ } catch (IOException e) {
-+ logger.error("Error backing up corrupt file" + file.getAbsolutePath(), e);
-+ }
-+ }
-+ // Paper end
-+
- class ChunkBuffer extends ByteArrayOutputStream {
-
- private final ChunkCoordIntPair b;
---
-2.22.0
-
diff --git a/patches/removed/1.15/0376-Handle-bad-chunks-more-gracefully.patch b/patches/removed/1.15/0376-Handle-bad-chunks-more-gracefully.patch
deleted file mode 100644
index 78a967a017..0000000000
--- a/patches/removed/1.15/0376-Handle-bad-chunks-more-gracefully.patch
+++ /dev/null
@@ -1,63 +0,0 @@
-From b03aa2b2428cf6e504fe4f069f208679e666676f Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Mon, 15 Apr 2019 02:24:52 +0100
-Subject: [PATCH] Handle bad chunks more gracefully
-
-Prior to this change the server would crash when attempting to load a
-chunk from a region with bad data.
-
-After this change the server will defer back to vanilla behavior. At
-this time, that means attempting to generate a chunk in its place
-(and occasionally just not generating anything and leaving small
-holes in the world (This statement might not be accurate as of 1.13.x)).
-
-Should Mojang choose to alter this behavior in the future, this change
-will simply defer to whatever that new behavior is.
-
-diff --git a/src/main/java/net/minecraft/server/RegionFileCache.java b/src/main/java/net/minecraft/server/RegionFileCache.java
-index c53518a47..6f34d8aea 100644
---- a/src/main/java/net/minecraft/server/RegionFileCache.java
-+++ b/src/main/java/net/minecraft/server/RegionFileCache.java
-@@ -171,8 +171,21 @@ public abstract class RegionFileCache implements AutoCloseable {
- private static NBTTagCompound readOversizedChunk(RegionFile regionfile, ChunkCoordIntPair chunkCoordinate) throws IOException {
- synchronized (regionfile) {
- try (DataInputStream datainputstream = regionfile.getReadStream(chunkCoordinate)) {
-- NBTTagCompound oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z);
-- NBTTagCompound chunk = NBTCompressedStreamTools.readNBT(datainputstream);
-+ // Paper start - Handle bad chunks more gracefully - also handle similarly with oversized data
-+ NBTTagCompound oversizedData = null;
-+
-+ try {
-+ oversizedData = regionfile.getOversizedData(chunkCoordinate.x, chunkCoordinate.z);
-+ } catch (Exception ex) {}
-+
-+ NBTTagCompound chunk;
-+
-+ try {
-+ chunk = NBTCompressedStreamTools.readNBT(datainputstream);
-+ } catch (final Exception ex) {
-+ return null;
-+ }
-+ // Paper end
- if (oversizedData == null) {
- return chunk;
- }
-@@ -231,8 +244,13 @@ public abstract class RegionFileCache implements AutoCloseable {
-
- try {
- if (datainputstream != null) {
-- nbttagcompound = NBTCompressedStreamTools.a(datainputstream);
-- return nbttagcompound;
-+ // Paper start - Handle bad chunks more gracefully
-+ try {
-+ return NBTCompressedStreamTools.a(datainputstream);
-+ } catch (Exception ex) {
-+ return null;
-+ }
-+ // Paper end
- }
-
- nbttagcompound = null;
---
-2.23.0
-
diff --git a/patches/removed/1.15/0403-Preserve-old-flush-on-save-flag-for-reliable-regionf.patch b/patches/removed/1.15/0403-Preserve-old-flush-on-save-flag-for-reliable-regionf.patch
deleted file mode 100644
index 13d75040c3..0000000000
--- a/patches/removed/1.15/0403-Preserve-old-flush-on-save-flag-for-reliable-regionf.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 98e10786918880c728f3afc751290cdf5eb14cf5 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 5 Aug 2019 08:24:01 -0700
-Subject: [PATCH] Preserve old flush on save flag for reliable regionfiles
-
-Originally this patch was in paper
-
-diff --git a/src/main/java/net/minecraft/server/RegionFile.java b/src/main/java/net/minecraft/server/RegionFile.java
-index b487e8060..a8c8ace46 100644
---- a/src/main/java/net/minecraft/server/RegionFile.java
-+++ b/src/main/java/net/minecraft/server/RegionFile.java
-@@ -349,7 +349,7 @@ public class RegionFile implements AutoCloseable {
- }
-
- // Spigot start - Make region files reliable
-- private static final boolean FLUSH_ON_SAVE = Boolean.getBoolean("spigot.flush-on-save");
-+ private static final boolean FLUSH_ON_SAVE = Boolean.getBoolean("spigot.flush-on-save") || Boolean.getBoolean("paper.flush-on-save"); // Paper - preserve old flag
- private void syncRegionFile() throws IOException {
- if (!FLUSH_ON_SAVE) {
- return;
---
-2.23.0
-
diff --git a/patches/removed/1.15/0412-Improve-POI-data-saving-logic.patch b/patches/removed/1.15/0412-Improve-POI-data-saving-logic.patch
deleted file mode 100644
index 52d36f27c1..0000000000
--- a/patches/removed/1.15/0412-Improve-POI-data-saving-logic.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From cf3689f611fad7d903831b63086deefad3cd8e92 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 19 Aug 2019 06:33:17 -0700
-Subject: [PATCH] Improve POI data saving logic
-
-- Do not unload data if world saving is disabled
-- Aggressively target unloading
-
-diff --git a/src/main/java/net/minecraft/server/VillagePlace.java b/src/main/java/net/minecraft/server/VillagePlace.java
-index 0e98b7803..fb99b4306 100644
---- a/src/main/java/net/minecraft/server/VillagePlace.java
-+++ b/src/main/java/net/minecraft/server/VillagePlace.java
-@@ -132,9 +132,12 @@ public class VillagePlace extends RegionFileSection<VillagePlaceSection> {
- // Paper start - async chunk io
- if (this.world == null) {
- super.a(booleansupplier);
-- } else {
-+ } else if (!this.world.isSavingDisabled()) { // Paper - only save if saving is enabled
- //super.a(booleansupplier); // re-implement below
-- while (!((RegionFileSection)this).d.isEmpty() && booleansupplier.getAsBoolean()) {
-+ // Paper start - target unloading aggressively
-+ int queueTarget = Math.min(this.d.size() - 100, (int)(this.d.size() * 0.96));
-+ while (!((RegionFileSection)this).d.isEmpty() && (this.d.size() > queueTarget || booleansupplier.getAsBoolean())) {
-+ // Paper end
- ChunkCoordIntPair chunkcoordintpair = SectionPosition.a(((RegionFileSection)this).d.firstLong()).u();
-
- NBTTagCompound data;
---
-2.23.0
-
diff --git a/patches/removed/1.15/0417-Avoid-Chunk-Lookups-for-Entity-TileEntity-Current-Ch.patch b/patches/removed/1.15/0417-Avoid-Chunk-Lookups-for-Entity-TileEntity-Current-Ch.patch
deleted file mode 100644
index 307f228f40..0000000000
--- a/patches/removed/1.15/0417-Avoid-Chunk-Lookups-for-Entity-TileEntity-Current-Ch.patch
+++ /dev/null
@@ -1,75 +0,0 @@
-From 95da400b09518a7f98754568f41aca73afddb92a Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 4 Jul 2018 03:39:51 -0400
-Subject: [PATCH] Avoid Chunk Lookups for Entity/TileEntity Current Chunk
-
-SPECIAL 1.14.1 NOTE:
-This patch caused a memory leak since the tile entity's chunk was set to null
-before it was removed. Ensure this issue is resolved!
-
-In many places where we simply want the current chunk the entity
-is in, instead of doing a hashmap lookup for it, we now have access
-to the object directly on the Entity/TileEntity object we can directly grab.
-
-Use that local value instead to reduce lookups in many hot places.
-
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index 2c92d3390a..3c3a504991 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -789,7 +789,7 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
- if (!tileentity.isRemoved() && tileentity.hasWorld()) {
- BlockPosition blockposition = tileentity.getPosition();
-
-- if (this.chunkProvider.a(blockposition) && this.getWorldBorder().a(blockposition)) {
-+ if (tileentity.getCurrentChunk() != null && this.getWorldBorder().a(blockposition)) { // Paper
- try {
- gameprofilerfiller.a(() -> {
- return String.valueOf(TileEntityTypes.a(tileentity.getTileType()));
-@@ -828,8 +828,11 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
- this.tileEntityListTick.remove(tileTickPosition--);
- // Spigot end
- //this.tileEntityList.remove(tileentity); // Paper - remove unused list
-- if (this.isLoaded(tileentity.getPosition())) {
-- this.getChunkAtWorldCoords(tileentity.getPosition()).removeTileEntity(tileentity.getPosition());
-+ // Paper start - use local chunk reference
-+ Chunk chunk = tileentity.getCurrentChunk();
-+ if (chunk != null) {
-+ chunk.removeTileEntity(tileentity.getPosition());
-+ // Paper end
- }
- }
- }
-@@ -849,8 +852,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
- }
- // CraftBukkit end */
-
-- if (this.isLoaded(tileentity1.getPosition())) {
-- Chunk chunk = this.getChunkAtWorldCoords(tileentity1.getPosition());
-+ Chunk chunk = tileentity1.getCurrentChunk(); // Paper
-+ if (chunk != null) { // Paper
- IBlockData iblockdata = chunk.getType(tileentity1.getPosition());
-
- chunk.setTileEntity(tileentity1.getPosition(), tileentity1);
-diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
-index 3739a95c5a..1e00908671 100644
---- a/src/main/java/net/minecraft/server/WorldServer.java
-+++ b/src/main/java/net/minecraft/server/WorldServer.java
-@@ -1390,9 +1390,12 @@ public class WorldServer extends World {
- }
-
- private void removeEntityFromChunk(Entity entity) {
-- IChunkAccess ichunkaccess = this.getChunkAt(entity.chunkX, entity.chunkZ, ChunkStatus.FULL, false);
-+ // Paper start
-+ if (!entity.inChunk) return;
-+ IChunkAccess ichunkaccess = this.getChunkIfLoaded(entity.chunkX, entity.chunkZ);
-+ // Paper start
-
-- if (ichunkaccess instanceof Chunk) {
-+ if (ichunkaccess != null) { // Paper
- ((Chunk) ichunkaccess).b(entity);
- }
-
---
-2.24.1
-
diff --git a/patches/removed/1.16/0238-Configurable-Bed-Search-Radius.patch b/patches/removed/1.16/0238-Configurable-Bed-Search-Radius.patch
deleted file mode 100644
index 4e8d577fbd..0000000000
--- a/patches/removed/1.16/0238-Configurable-Bed-Search-Radius.patch
+++ /dev/null
@@ -1,159 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 4 Jul 2018 15:22:06 -0400
-Subject: [PATCH] Configurable Bed Search Radius
-
-Allows you to increase how far to check for a safe place to respawn
-a player near their bed, allowing a better chance to respawn the
-player at their bed should it of became obstructed.
-
-Defaults to vanilla 1.
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index 6352051ab937d4d365e823a7112e76dc3ec34225..d6a3d882e375ac5a2b6ec8920532db615f4fe4ef 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -372,4 +372,15 @@ public class PaperWorldConfig {
- private void scanForLegacyEnderDragon() {
- scanForLegacyEnderDragon = getBoolean("game-mechanics.scan-for-legacy-ender-dragon", true);
- }
-+
-+ public int bedSearchRadius = 1;
-+ private void bedSearchRadius() {
-+ bedSearchRadius = getInt("bed-search-radius", 1);
-+ if (bedSearchRadius < 1) {
-+ bedSearchRadius = 1;
-+ }
-+ if (bedSearchRadius > 1) {
-+ log("Bed Search Radius: " + bedSearchRadius);
-+ }
-+ }
- }
-diff --git a/src/main/java/net/minecraft/server/BlockBed.java b/src/main/java/net/minecraft/server/BlockBed.java
-index 7604d79468ce8d7d1a4f45872a5db0c700419029..e7bd9061cceba284443b75cc5506e1b9f2ef42e8 100644
---- a/src/main/java/net/minecraft/server/BlockBed.java
-+++ b/src/main/java/net/minecraft/server/BlockBed.java
-@@ -199,6 +199,8 @@ public class BlockBed extends BlockFacingHorizontal implements ITileEntity {
-
- public static Optional<Vec3D> a(EntityTypes<?> entitytypes, IWorldReader iworldreader, BlockPosition blockposition, int i) {
- EnumDirection enumdirection = (EnumDirection) iworldreader.getType(blockposition).get(BlockBed.FACING);
-+ // Paper start - configurable bed search radius
-+ if (entitytypes == EntityTypes.PLAYER) return findSafePosition(entitytypes, (World) iworldreader, enumdirection, blockposition);
- int j = blockposition.getX();
- int k = blockposition.getY();
- int l = blockposition.getZ();
-@@ -228,7 +230,104 @@ public class BlockBed extends BlockFacingHorizontal implements ITileEntity {
- return Optional.empty();
- }
-
-- public static Optional<Vec3D> a(EntityTypes<?> entitytypes, IWorldReader iworldreader, BlockPosition blockposition) {
-+ private static Optional<Vec3D> findSafePosition(EntityTypes<?> entitytypes, World world, EnumDirection updirection, BlockPosition blockposition){
-+ int radius = world.paperConfig.bedSearchRadius;
-+ double angle = Math.PI / 2;
-+ int tmpX = (int)(updirection.getAdjacentX() * Math.cos(angle) - updirection.getAdjacentZ() * Math.sin(angle));
-+ int tmpZ = (int)(updirection.getAdjacentX() * Math.sin(angle) + updirection.getAdjacentZ() * Math.cos(angle));
-+
-+ EnumDirection rightDirection = EnumDirection.a(tmpX, 0, tmpZ);
-+ EnumDirection downDirection = updirection.opposite();
-+ EnumDirection leftDirection = rightDirection.opposite();
-+
-+ EnumDirection[] corePositionOutDirection = new EnumDirection[6];
-+ corePositionOutDirection[0] = updirection;
-+ corePositionOutDirection[1] = leftDirection;
-+ corePositionOutDirection[2] = leftDirection;
-+ corePositionOutDirection[3] = downDirection;
-+ corePositionOutDirection[4] = rightDirection;
-+ corePositionOutDirection[5] = rightDirection;
-+
-+ BlockPosition[] corePosition = new BlockPosition[6];
-+ corePosition[0] = blockposition.add(updirection.getAdjacentX(), 0, updirection.getAdjacentZ());
-+ corePosition[1] = blockposition.add(leftDirection.getAdjacentX(), 0, leftDirection.getAdjacentZ());
-+ corePosition[2] = corePosition[1].add(downDirection.getAdjacentX(), 0, downDirection.getAdjacentZ());
-+ corePosition[3] = blockposition.add(2 * downDirection.getAdjacentX(), 0, 2 * downDirection.getAdjacentZ());
-+ corePosition[5] = blockposition.add(rightDirection.getAdjacentX(), 0, rightDirection.getAdjacentZ());
-+ corePosition[4] = corePosition[5].add(downDirection.getAdjacentX(), 0, downDirection.getAdjacentZ());
-+
-+ BlockPosition[] tmpPosition = new BlockPosition[8];
-+ EnumDirection[] tmpPositionDirection = new EnumDirection[8];
-+ tmpPositionDirection[0] = rightDirection;
-+ tmpPositionDirection[1] = leftDirection;
-+ tmpPositionDirection[2] = updirection;
-+ tmpPositionDirection[3] = downDirection;
-+ tmpPositionDirection[4] = leftDirection;
-+ tmpPositionDirection[5] = rightDirection;
-+ tmpPositionDirection[6] = downDirection;
-+ tmpPositionDirection[7] = updirection;
-+
-+ BlockPosition pos;
-+ Optional<Vec3D> vector;
-+ for (int r = 1; r <= radius; r++) {
-+ int h = 0;
-+ while (h <= 1) {
-+ int numIterated = 0;
-+ for (int index = (int)(Math.random() * corePosition.length); numIterated < corePosition.length; index = (index+1) % corePosition.length) {
-+ numIterated++;
-+
-+ pos = corePosition[index].add(0, h, 0);
-+ vector = isSafeRespawn(entitytypes, world, pos, 0);
-+ if (vector.isPresent()) {
-+ return vector;
-+ }
-+ }
-+ tmpPosition[0] = corePosition[0].add(0, h, 0);
-+ tmpPosition[1] = corePosition[0].add(0, h, 0);
-+ tmpPosition[2] = corePosition[1].add(0, h, 0);
-+ tmpPosition[3] = corePosition[2].add(0, h, 0);
-+ tmpPosition[4] = corePosition[3].add(0, h, 0);
-+ tmpPosition[5] = corePosition[3].add(0, h, 0);
-+ tmpPosition[6] = corePosition[4].add(0, h, 0);
-+ tmpPosition[7] = corePosition[5].add(0, h, 0);
-+ for (int rr = 1; rr <= r; rr++){
-+ numIterated = 0;
-+ for (int index = (int)(Math.random() * tmpPosition.length); numIterated < tmpPosition.length; index = (index+1) % tmpPosition.length) {
-+ numIterated++;
-+ tmpPosition[index] = tmpPosition[index].add(tmpPositionDirection[index].getAdjacentX(), 0, tmpPositionDirection[index].getAdjacentZ());
-+ pos = tmpPosition[index];
-+
-+ vector = isSafeRespawn(entitytypes, world, pos, 0);
-+ if (vector.isPresent()) {
-+ return vector;
-+ }
-+ }
-+ }
-+ switch (h) {
-+ case 0:
-+ h = -1;
-+ break;
-+ case -1:
-+ h = -2;
-+ break;
-+ case -2:
-+ h = Integer.MAX_VALUE;
-+ break;
-+ }
-+ }
-+ for (int index = 0; index < corePosition.length; index++) {
-+ EnumDirection tmp = corePositionOutDirection[index];
-+ corePosition[index] = corePosition[index].add(tmp.getAdjacentX(), 0, tmp.getAdjacentZ());
-+ }
-+ }
-+ return Optional.empty();
-+ }
-+ // Paper end
-+
-+ // Paper start -- add maxBelow param
-+ public static Optional<Vec3D> a(EntityTypes<?> entitytypes, IWorldReader iworldreader, BlockPosition blockposition) { return isSafeRespawn(entitytypes, iworldreader, blockposition, 2); }
-+ public static Optional<Vec3D> isSafeRespawn(EntityTypes<?> entitytypes, IWorldReader iworldreader, BlockPosition blockposition, int maxBelow) {
-+ // Paper end
- VoxelShape voxelshape = iworldreader.getType(blockposition).getCollisionShape(iworldreader, blockposition);
-
- if (voxelshape.c(EnumDirection.EnumAxis.Y) > 0.4375D) {
-@@ -236,7 +335,7 @@ public class BlockBed extends BlockFacingHorizontal implements ITileEntity {
- } else {
- BlockPosition.MutableBlockPosition blockposition_mutableblockposition = blockposition.i();
-
-- while (blockposition_mutableblockposition.getY() >= 0 && blockposition.getY() - blockposition_mutableblockposition.getY() <= 2 && iworldreader.getType(blockposition_mutableblockposition).getCollisionShape(iworldreader, blockposition_mutableblockposition).isEmpty()) {
-+ while (blockposition_mutableblockposition.getY() >= 0 && blockposition.getY() - blockposition_mutableblockposition.getY() <= maxBelow && iworldreader.getType(blockposition_mutableblockposition).getCollisionShape(iworldreader, blockposition_mutableblockposition).isEmpty()) { // Paper -- configurable max distance to search below
- blockposition_mutableblockposition.c(EnumDirection.DOWN);
- }
-
diff --git a/patches/removed/1.16/0298-Support-Overriding-World-Seeds.patch b/patches/removed/1.16/0298-Support-Overriding-World-Seeds.patch
deleted file mode 100644
index 2ff8415988..0000000000
--- a/patches/removed/1.16/0298-Support-Overriding-World-Seeds.patch
+++ /dev/null
@@ -1,99 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Mon, 17 Sep 2018 23:05:31 -0400
-Subject: [PATCH] Support Overriding World Seeds
-
-Allows you to add to paper.yml
-
-seed-overrides:
- world_name: some seed value
-
-This will ignore every where a seed is set/created/loaded and force
-a world to use the specified seed.
-
-This seed will end up being saved to the world data file, so it is
-a permanent change in that it won't go back if you remove it from paper.yml
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-index 214b577b326bc794fa3721deb6171228dd4f25e6..559e6b42ba5bf0ea92cccbabd2ef1d4c27b03064 100644
---- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-@@ -11,6 +11,7 @@ import java.lang.reflect.Modifier;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-+import java.util.Set;
- import java.util.concurrent.TimeUnit;
- import java.util.logging.Level;
- import java.util.regex.Pattern;
-@@ -19,6 +20,7 @@ import com.google.common.collect.Lists;
- import net.minecraft.server.MinecraftServer;
- import org.bukkit.Bukkit;
- import org.bukkit.command.Command;
-+import org.bukkit.configuration.ConfigurationSection;
- import org.bukkit.configuration.InvalidConfigurationException;
- import org.bukkit.configuration.file.YamlConfiguration;
- import co.aikar.timings.Timings;
-@@ -310,4 +312,23 @@ public class PaperConfig {
- }
- tabSpamLimit = getInt("settings.spam-limiter.tab-spam-limit", tabSpamLimit);
- }
-+
-+ public static Map<String, Long> seedOverride = new java.util.HashMap<>();
-+ private static void worldSeedOverrides() {
-+ ConfigurationSection seeds = config.getConfigurationSection("seed-overrides");
-+ if (seeds != null) {
-+ TimingsManager.hiddenConfigs.add("seed-overrides");
-+ for (String key : seeds.getKeys(false)) {
-+ String seedString = seeds.getString(key);
-+ long seed;
-+ try {
-+ seed = Long.parseLong(seedString);
-+ } catch (Exception e) {
-+ seed = (long) seedString.hashCode();
-+ }
-+ log("Seed Override: " + key + " => " + seed);
-+ seedOverride.put(key, seed);
-+ }
-+ }
-+ }
- }
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 3b89f62ab0522d23f47fd59c2f06fa7d0eacb7af..85f989829b5ad1d7681b57cf68519a4806b26ea1 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -378,7 +378,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
- this.convertWorld(name); // Run conversion now
-
- org.bukkit.generator.ChunkGenerator gen = this.server.getGenerator(name);
-- WorldSettings worldsettings = new WorldSettings(i, this.getGamemode(), this.getGenerateStructures(), this.isHardcore(), worldtype);
-+ WorldSettings worldsettings = new WorldSettings(com.destroystokyo.paper.PaperConfig.seedOverride.getOrDefault(name, i), this.getGamemode(), this.getGenerateStructures(), this.isHardcore(), worldtype); // Paper
- worldsettings.setGeneratorSettings(jsonelement);
-
- if (j == 0) {
-diff --git a/src/main/java/net/minecraft/server/WorldData.java b/src/main/java/net/minecraft/server/WorldData.java
-index 561b6d94696eebc255279f45d2ca6b88b2490f78..95518e54d1fd7ada51df1cdc3562affccd48bfcb 100644
---- a/src/main/java/net/minecraft/server/WorldData.java
-+++ b/src/main/java/net/minecraft/server/WorldData.java
-@@ -127,7 +127,7 @@ public class WorldData {
- this.d = nbttagcompound2.getBoolean("Snapshot");
- }
-
-- this.e = nbttagcompound.getLong("RandomSeed");
-+ this.e = com.destroystokyo.paper.PaperConfig.seedOverride.getOrDefault(nbttagcompound.getString("LevelName"), nbttagcompound.getLong("RandomSeed")); // Paper
- if (nbttagcompound.hasKeyOfType("generatorName", 8)) {
- String s = nbttagcompound.getString("generatorName");
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index d15a857b30511bda9bbeddf651b4633e4bea473d..011d0927da7a2a67dcd6d75e3af07d38f30acf81 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-@@ -1016,7 +1016,7 @@ public final class CraftServer implements Server {
- WorldSettings worldSettings;
- // See MinecraftServer.a(String, String, long, WorldType, JsonElement)
- if (worlddata == null) {
-- worldSettings = new WorldSettings(creator.seed(), EnumGamemode.getById(getDefaultGameMode().getValue()), generateStructures, hardcore, type);
-+ worldSettings = new WorldSettings(com.destroystokyo.paper.PaperConfig.seedOverride.getOrDefault(name, creator.seed()), EnumGamemode.getById(getDefaultGameMode().getValue()), generateStructures, hardcore, type); // Paper
- JsonElement parsedSettings = new JsonParser().parse(creator.generatorSettings());
- if (parsedSettings.isJsonObject()) {
- worldSettings.setGeneratorSettings(parsedSettings.getAsJsonObject());
diff --git a/patches/removed/1.16/0511-Implement-JellySquid-s-Entity-Collision-optimisation.patch b/patches/removed/1.16/0511-Implement-JellySquid-s-Entity-Collision-optimisation.patch
deleted file mode 100644
index d1d02e180f..0000000000
--- a/patches/removed/1.16/0511-Implement-JellySquid-s-Entity-Collision-optimisation.patch
+++ /dev/null
@@ -1,101 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: JellySquid <[email protected]>
-Date: Sat, 9 May 2020 16:25:21 -0400
-Subject: [PATCH] Implement JellySquid's Entity Collision optimisations patch
-
-Optimizes Full Block voxel collisions, and removes streams from Entity collisions
-
-Original code by JellySquid, licensed under GNU Lesser General Public License v3.0
-you can find the original code on https://github.com/jellysquid3/lithium-fabric/tree/1.15.x/fabric (Yarn mappings)
-
-diff --git a/src/main/java/net/minecraft/server/ICollisionAccess.java b/src/main/java/net/minecraft/server/ICollisionAccess.java
-index 3eefbf4d5f10b53f930759a0afa5661253b92c60..5e20dba0d011d20b714d784cb4a545a05bbf6f9c 100644
---- a/src/main/java/net/minecraft/server/ICollisionAccess.java
-+++ b/src/main/java/net/minecraft/server/ICollisionAccess.java
-@@ -115,11 +115,24 @@ public interface ICollisionAccess extends IBlockAccess {
-
- if ((j2 != 1 || iblockdata.f()) && (j2 != 2 || iblockdata.getBlock() == Blocks.MOVING_PISTON)) {
- VoxelShape voxelshape2 = iblockdata.b((IBlockAccess) ICollisionAccess.this, blockposition_mutableblockposition, voxelshapecollision);
-- VoxelShape voxelshape3 = voxelshape2.a((double) k1, (double) l1, (double) i2);
-
-- if (VoxelShapes.c(voxelshape, voxelshape3, OperatorBoolean.AND)) {
-- consumer.accept(voxelshape3);
-- return true;
-+ // Paper start - Lithium Collision Optimizations
-+ if (voxelshape2 == VoxelShapes.empty()) {
-+ continue;
-+ }
-+
-+ if (voxelshape2 == VoxelShapes.fullCube()) {
-+ if (axisalignedbb.intersects(x, y, z, x + 1.0D, y + 1.0D, z + 1.0D)) {
-+ consumer.accept(voxelshape2.offset(x, y, z));
-+ return true;
-+ }
-+ } else {
-+ VoxelShape shape = voxelshape2.offset(x, y, z);
-+ if (VoxelShapes.applyOperation(shape, voxelshape, OperatorBoolean.AND)) {
-+ consumer.accept(shape);
-+ return true;
-+ }
-+ // Paper end
- }
- }
- }
-diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java
-index 5135308fb6137a34ed6fd061f0a210de6de4e81c..d434aaaaf0ab6a18ab0fe5ad0bf8ed4662f49120 100644
---- a/src/main/java/net/minecraft/server/IEntityAccess.java
-+++ b/src/main/java/net/minecraft/server/IEntityAccess.java
-@@ -52,20 +52,41 @@ public interface IEntityAccess {
- // Paper end - optimise hard collision
-
- default Stream<VoxelShape> b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set<Entity> set) {
-- if (axisalignedbb.a() < 1.0E-7D) {
-+ // Paper start - remove streams from entity collision
-+ if (axisalignedbb.getAverageSideLength() < 1.0E-7D) {
- return Stream.empty();
-- } else {
-- AxisAlignedBB axisalignedbb1 = axisalignedbb.g(1.0E-7D);
-- Stream<AxisAlignedBB> stream = ((entity != null && entity.hardCollides()) ? this.getEntities(entity, axisalignedbb) : this.getHardCollidingEntities(entity, axisalignedbb1)).stream().filter((entity1) -> { // Paper - decompile fix // Paper - optimise hard collision
-- return !set.contains(entity1);
-- }).filter((entity1) -> {
-- return entity == null || !entity.isSameVehicle(entity1);
-- }).flatMap((entity1) -> {
-- return Stream.of(entity1.au(), entity == null ? null : entity.j(entity1)); // Paper - optimise hard collision - diff on change, these are the methods that only hard colliding entities override
-- }).filter(Objects::nonNull);
--
-- return stream.filter(axisalignedbb1::c).map(VoxelShapes::a);
-+
- }
-+ AxisAlignedBB selection = axisalignedbb.grow(1.0E-7D);
-+ List<Entity> entities = entity != null && entity.hardCollides() ? getEntities(entity, selection) : getHardCollidingEntities(entity, selection);
-+ List<VoxelShape> shapes = new java.util.ArrayList<>();
-+
-+ for (Entity otherEntity : entities) {
-+ if (!set.isEmpty() && set.contains(otherEntity)) {
-+ continue;
-+ }
-+
-+ if (entity != null && entity.isSameVehicle(otherEntity)) {
-+ continue;
-+ }
-+
-+ AxisAlignedBB otherEntityBox = otherEntity.getCollisionBox();
-+
-+ if (otherEntityBox != null && selection.intersects(otherEntityBox)) {
-+ shapes.add(VoxelShapes.of(otherEntityBox));
-+ }
-+
-+ if (entity != null) {
-+ AxisAlignedBB otherEntityHardBox = entity.getHardCollisionBox(otherEntity);
-+
-+ if (otherEntityHardBox != null && selection.intersects(otherEntityHardBox)) {
-+ shapes.add(VoxelShapes.of(otherEntityHardBox));
-+ }
-+ }
-+ }
-+
-+ return shapes.stream();
-+ // Paper end
- }
-
- @Nullable
diff --git a/patches/removed/1.16/0512-Remove-some-Streams-usage-in-Entity-Collision.patch b/patches/removed/1.16/0512-Remove-some-Streams-usage-in-Entity-Collision.patch
deleted file mode 100644
index 354e5f089a..0000000000
--- a/patches/removed/1.16/0512-Remove-some-Streams-usage-in-Entity-Collision.patch
+++ /dev/null
@@ -1,178 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 9 May 2020 18:36:27 -0400
-Subject: [PATCH] Remove some Streams usage in Entity Collision
-
-While there is more down the collision system, remove some of the wrapping
-Spliterator stuff as even this wrapper stream has shown up in profiling.
-
-With other collision optimizations, we might also even avoid inner streams too.
-
-diff --git a/src/main/java/net/minecraft/server/GeneratorAccess.java b/src/main/java/net/minecraft/server/GeneratorAccess.java
-index e865a5694f78fb9273a0625ab2c30b87d0711a90..5648ba73c533f622c35c808decdb305f8a1cf6b0 100644
---- a/src/main/java/net/minecraft/server/GeneratorAccess.java
-+++ b/src/main/java/net/minecraft/server/GeneratorAccess.java
-@@ -52,6 +52,7 @@ public interface GeneratorAccess extends IEntityAccess, IWorldReader, VirtualLev
- this.a((EntityHuman) null, i, blockposition, j);
- }
-
-+ @Override default java.util.List<VoxelShape> getEntityCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set<Entity> set, boolean returnFast) {return IEntityAccess.super.getEntityCollisions(entity, axisalignedbb, set, returnFast); } // Paper
- @Override
- default Stream<VoxelShape> b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set<Entity> set) {
- return IEntityAccess.super.b(entity, axisalignedbb, set);
-diff --git a/src/main/java/net/minecraft/server/ICollisionAccess.java b/src/main/java/net/minecraft/server/ICollisionAccess.java
-index 5e20dba0d011d20b714d784cb4a545a05bbf6f9c..5a21205a49606b294de4cd27b60438c6a5b3c526 100644
---- a/src/main/java/net/minecraft/server/ICollisionAccess.java
-+++ b/src/main/java/net/minecraft/server/ICollisionAccess.java
-@@ -44,19 +44,40 @@ public interface ICollisionAccess extends IBlockAccess {
-
- default boolean a(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set<Entity> set) {
- try { if (entity != null) entity.collisionLoadChunks = true; // Paper
-- return this.c(entity, axisalignedbb, set).allMatch(VoxelShape::isEmpty);
-+ // Paper start - reduce stream usage
-+ java.util.List<VoxelShape> blockCollisions = getBlockCollision(entity, axisalignedbb, true);
-+ for (int i = 0; i < blockCollisions.size(); i++) {
-+ VoxelShape blockCollision = blockCollisions.get(i);
-+ if (!blockCollision.isEmpty()) {
-+ return false;
-+ }
-+ }
-+ return getEntityCollisions(entity, axisalignedbb, set, true).isEmpty();
-+ // Paper end
- } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper
- }
-
-+ default java.util.List<VoxelShape> getEntityCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set<Entity> set, boolean returnFast) { return java.util.Collections.emptyList(); } // Paper
- default Stream<VoxelShape> b(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set<Entity> set) {
- return Stream.empty();
- }
-
- default Stream<VoxelShape> c(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set<Entity> set) {
-- return Streams.concat(new Stream[]{this.b(entity, axisalignedbb), this.b(entity, axisalignedbb, set)});
-+ // Paper start - reduce stream usage
-+ java.util.List<VoxelShape> blockCollisions = getBlockCollision(entity, axisalignedbb, false);
-+ java.util.List<VoxelShape> entityCollisions = getEntityCollisions(entity, axisalignedbb, set, false);
-+ return Stream.concat(blockCollisions.stream(), entityCollisions.stream());
-+ // Paper end
- }
-
- default Stream<VoxelShape> b(@Nullable final Entity entity, AxisAlignedBB axisalignedbb) {
-+ // Paper start - reduce stream usage
-+ java.util.List<VoxelShape> collision = getBlockCollision(entity, axisalignedbb, false);
-+ return !collision.isEmpty() ? collision.stream() : Stream.empty();
-+ }
-+
-+ default java.util.List<VoxelShape> getBlockCollision(@Nullable final Entity entity, AxisAlignedBB axisalignedbb, boolean returnFast) {
-+ // Paper end
- int i = MathHelper.floor(axisalignedbb.minX - 1.0E-7D) - 1;
- int j = MathHelper.floor(axisalignedbb.maxX + 1.0E-7D) + 1;
- int k = MathHelper.floor(axisalignedbb.minY - 1.0E-7D) - 1;
-@@ -68,19 +89,19 @@ public interface ICollisionAccess extends IBlockAccess {
- final BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition();
- final VoxelShape voxelshape = VoxelShapes.a(axisalignedbb);
-
-- return StreamSupport.stream(new AbstractSpliterator<VoxelShape>(Long.MAX_VALUE, 1280) {
-- boolean a = entity == null;
--
-- public boolean tryAdvance(Consumer<? super VoxelShape> consumer) {
-- if (!this.a) {
-- this.a = true;
-+ // Paper start - reduce stream usage (this part done by Aikar)
-+ java.util.List<VoxelShape> collisions = new java.util.ArrayList<>();
-+ if (true) {//return StreamSupport.stream(new AbstractSpliterator<VoxelShape>(Long.MAX_VALUE, 1280) {
-+ if (true) { //public boolean tryAdvance(Consumer<? super VoxelShape> consumer) {*/ // Paper
-+ if (entity != null) {
-+ // Paper end
- VoxelShape voxelshape1 = ICollisionAccess.this.getWorldBorder().a();
- boolean flag = VoxelShapes.c(voxelshape1, VoxelShapes.a(entity.getBoundingBox().shrink(1.0E-7D)), OperatorBoolean.AND);
- boolean flag1 = VoxelShapes.c(voxelshape1, VoxelShapes.a(entity.getBoundingBox().g(1.0E-7D)), OperatorBoolean.AND);
-
- if (!flag && flag1) {
-- consumer.accept(voxelshape1);
-- return true;
-+ collisions.add(voxelshape1); // Paper
-+ if (returnFast) return collisions;
- }
- }
-
-@@ -104,9 +125,8 @@ public interface ICollisionAccess extends IBlockAccess {
- );
- if (iblockdata == null) {
- if (!(entity instanceof EntityPlayer) || entity.world.paperConfig.preventMovingIntoUnloadedChunks) {
-- VoxelShape voxelshape3 = VoxelShapes.of(far ? entity.getBoundingBox() : new AxisAlignedBB(new BlockPosition(x, y, z)));
-- consumer.accept(voxelshape3);
-- return true;
-+ collisions.add(VoxelShapes.of(far ? entity.getBoundingBox() : new AxisAlignedBB(new BlockPosition(x, y, z))));
-+ if (returnFast) return collisions;
- }
- } else {
- //blockposition_mutableblockposition.d(k1, l1, i2); // moved up
-@@ -123,14 +143,14 @@ public interface ICollisionAccess extends IBlockAccess {
-
- if (voxelshape2 == VoxelShapes.fullCube()) {
- if (axisalignedbb.intersects(x, y, z, x + 1.0D, y + 1.0D, z + 1.0D)) {
-- consumer.accept(voxelshape2.offset(x, y, z));
-- return true;
-+ collisions.add(voxelshape2.offset(x, y, z));
-+ if (returnFast) return collisions;
- }
- } else {
- VoxelShape shape = voxelshape2.offset(x, y, z);
- if (VoxelShapes.applyOperation(shape, voxelshape, OperatorBoolean.AND)) {
-- consumer.accept(shape);
-- return true;
-+ collisions.add(shape);
-+ if (returnFast) return collisions;
- }
- // Paper end
- }
-@@ -139,8 +159,9 @@ public interface ICollisionAccess extends IBlockAccess {
- }
- }
-
-- return false;
-+ //return false; // Paper
- }
-- }, false);
-+ } //}, false);
-+ return collisions; // Paper
- }
- }
-diff --git a/src/main/java/net/minecraft/server/IEntityAccess.java b/src/main/java/net/minecraft/server/IEntityAccess.java
-index d434aaaaf0ab6a18ab0fe5ad0bf8ed4662f49120..3bc57ef91d557383178533b0cc87a71a521d6b3e 100644
---- a/src/main/java/net/minecraft/server/IEntityAccess.java
-+++ b/src/main/java/net/minecraft/server/IEntityAccess.java
-@@ -55,8 +55,10 @@ public interface IEntityAccess {
- // Paper start - remove streams from entity collision
- if (axisalignedbb.getAverageSideLength() < 1.0E-7D) {
- return Stream.empty();
--
- }
-+ return getEntityCollisions(entity, axisalignedbb, set, false).stream();
-+ }
-+ default List<VoxelShape> getEntityCollisions(@Nullable Entity entity, AxisAlignedBB axisalignedbb, Set<Entity> set, boolean returnFast) {
- AxisAlignedBB selection = axisalignedbb.grow(1.0E-7D);
- List<Entity> entities = entity != null && entity.hardCollides() ? getEntities(entity, selection) : getHardCollidingEntities(entity, selection);
- List<VoxelShape> shapes = new java.util.ArrayList<>();
-@@ -74,6 +76,7 @@ public interface IEntityAccess {
-
- if (otherEntityBox != null && selection.intersects(otherEntityBox)) {
- shapes.add(VoxelShapes.of(otherEntityBox));
-+ if (returnFast) return shapes;
- }
-
- if (entity != null) {
-@@ -81,11 +84,12 @@ public interface IEntityAccess {
-
- if (otherEntityHardBox != null && selection.intersects(otherEntityHardBox)) {
- shapes.add(VoxelShapes.of(otherEntityHardBox));
-+ if (returnFast) return shapes;
- }
- }
- }
-
-- return shapes.stream();
-+ return shapes;
- // Paper end
- }
-
diff --git a/patches/removed/1.16/0530-Optimize-Villagers.patch b/patches/removed/1.16/0530-Optimize-Villagers.patch
deleted file mode 100644
index d7cec2bf86..0000000000
--- a/patches/removed/1.16/0530-Optimize-Villagers.patch
+++ /dev/null
@@ -1,273 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Tue, 26 May 2020 21:32:05 -0400
-Subject: [PATCH] Optimize Villagers
-
-This change reimplements the entire BehaviorFindPosition method to
-get rid of all of the streams, and implement the logic in a more sane way.
-
-We keep vanilla behavior 100% the same with this change, just wrote more
-optimal, as we can abort iterating POI's as soon as we find a match....
-
-One slight change is that Minecraft adds a random delay before a POI is
-attempted again. I've increased the amount of that delay based on the distance
-to said POI, so farther POI's will not be attempted as often.
-
-Additionally, we spiral out, so we favor local POI's before we ever favor farther POI's.
-
-We also try to pathfind 1 POI at a time instead of collecting multiple POI's then tossing them
-all to the pathfinder, so that once we get a match we can return before even looking at other
-POI's.
-
-This benefits us in that ideally, a villager will constantly find the near POI's and
-not even try to pathfind to the farther POI. Trying to pathfind to distant POI's is
-what causes significant lag.
-
-Other improvements here is to stop spamming the POI manager with empty nullables.
-Vanilla used them to represent if they needed to load POI data off disk or not.
-
-Well, we load POI data async on chunk load, so we have it, and we surely do not ever
-want to load POI data sync either for unloaded chunks!
-
-So this massively reduces object count in the POI hashmaps, resulting in less hash collions,
-and also less memory use.
-
-Additionally, unemployed villagers were using significant time due to major ineffeciency in
-the code rebuilding data that is static every single invocation for every POI type...
-
-So we cache that and only rebuild it if professions change, which should be never unless
-a plugin manipulates and adds custom professions, which it will handle by rebuilding.
-
-diff --git a/src/main/java/net/minecraft/server/BehaviorFindPosition.java b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
-index 35eb3a5a61145e94d5b0c77c0eb13bfa46fac23b..6861b1a345496a83900b0ef702ba34315a030ef6 100644
---- a/src/main/java/net/minecraft/server/BehaviorFindPosition.java
-+++ b/src/main/java/net/minecraft/server/BehaviorFindPosition.java
-@@ -29,8 +29,55 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
-
- protected void a(WorldServer worldserver, EntityCreature entitycreature, long i) {
- this.f = 0;
-- this.d = worldserver.getTime() + (long) worldserver.getRandom().nextInt(20);
-+ this.d = worldserver.getTime() + (long) java.util.concurrent.ThreadLocalRandom.current().nextInt(20); // Paper
- VillagePlace villageplace = worldserver.B();
-+
-+ // Paper start - replace implementation completely
-+ BlockPosition blockposition2 = new BlockPosition(entitycreature);
-+ int dist = 48;
-+ int requiredDist = dist * dist;
-+ int cdist = Math.floorDiv(dist, 16);
-+ Predicate<VillagePlaceType> predicate = this.a.c();
-+ int maxPoiAttempts = 4;
-+ int poiAttempts = 0;
-+ OUT:
-+ for (ChunkCoordIntPair chunkcoordintpair : MCUtil.getSpiralOutChunks(blockposition2, cdist)) {
-+ for (int i1 = 0; i1 < 16; i1++) {
-+ java.util.Optional<VillagePlaceSection> section = villageplace.getSection(SectionPosition.a(chunkcoordintpair, i1).v());
-+ if (section == null || !section.isPresent()) continue;
-+ for (java.util.Map.Entry<VillagePlaceType, java.util.Set<VillagePlaceRecord>> e : section.get().getRecords().entrySet()) {
-+ if (!predicate.test(e.getKey())) continue;
-+ for (VillagePlaceRecord record : e.getValue()) {
-+ if (!record.hasVacancy()) continue;
-+
-+ BlockPosition pos = record.getPosition();
-+ long key = pos.asLong();
-+ if (this.e.containsKey(key)) {
-+ continue;
-+ }
-+ double poiDist = pos.distanceSquared(blockposition2);
-+ if (poiDist <= (double) requiredDist) {
-+ this.e.put(key, (long) (this.d + Math.sqrt(poiDist) * 4)); // use dist instead of 40 to blacklist longer if farther distance
-+ ++poiAttempts;
-+ PathEntity pathentity = entitycreature.getNavigation().a(com.google.common.collect.ImmutableSet.of(pos), 8, false, this.a.d());
-+
-+ if (pathentity != null && pathentity.h()) {
-+ record.decreaseVacancy();
-+ GlobalPos globalPos = GlobalPos.create(worldserver.getWorldProvider().getDimensionManager(), pos);
-+ entitycreature.getBehaviorController().setMemory(this.b, globalPos);
-+ break OUT;
-+ }
-+ if (poiAttempts >= maxPoiAttempts) {
-+ break OUT;
-+ }
-+ }
-+ }
-+ }
-+ }
-+ }
-+ // Clean up - vanilla does this only when it runs out, but that would push it to try farther POI's...
-+ this.e.long2LongEntrySet().removeIf((entry) -> entry.getLongValue() < this.d);
-+ /*
- Predicate<BlockPosition> predicate = (blockposition) -> {
- long j = blockposition.asLong();
-
-@@ -61,6 +108,6 @@ public class BehaviorFindPosition extends Behavior<EntityCreature> {
- return entry.getLongValue() < this.d;
- });
- }
--
-+ */ // Paper end
- }
- }
-diff --git a/src/main/java/net/minecraft/server/RegionFileSection.java b/src/main/java/net/minecraft/server/RegionFileSection.java
-index a6d8ef5eb44f3f851a3a1be4032ca21ab1d7f2b2..c3e8a0145d63843736d2060f978cdf38df359563 100644
---- a/src/main/java/net/minecraft/server/RegionFileSection.java
-+++ b/src/main/java/net/minecraft/server/RegionFileSection.java
-@@ -51,29 +51,15 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
-
- @Nullable
- protected Optional<R> c(long i) {
-- return (Optional) this.c.get(i);
-+ return this.c.getOrDefault(i, Optional.empty()); // Paper
- }
-
-+ protected final Optional<R> getSection(long i) { return d(i); } // Paper - OBFHELPER
- protected Optional<R> d(long i) {
-- SectionPosition sectionposition = SectionPosition.a(i);
--
-- if (this.b(sectionposition)) {
-- return Optional.empty();
-- } else {
-- Optional<R> optional = this.c(i);
--
-- if (optional != null) {
-- return optional;
-- } else {
-- this.b(sectionposition.u());
-- optional = this.c(i);
-- if (optional == null) {
-- throw (IllegalStateException) SystemUtils.c(new IllegalStateException());
-- } else {
-- return optional;
-- }
-- }
-- }
-+ // Paper start - replace method - never load POI data sync, we load this in chunk load already, reduce ops
-+ // If it's an unloaded chunk, well too bad.
-+ return this.c(i);
-+ // Paper end
- }
-
- protected boolean b(SectionPosition sectionposition) {
-@@ -117,7 +103,7 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
- private <T> void a(ChunkCoordIntPair chunkcoordintpair, DynamicOps<T> dynamicops, @Nullable T t0) {
- if (t0 == null) {
- for (int i = 0; i < 16; ++i) {
-- this.c.put(SectionPosition.a(chunkcoordintpair, i).v(), Optional.empty());
-+ //this.c.put(SectionPosition.a(chunkcoordintpair, i).v(), Optional.empty()); // Paper - NO!!!
- }
- } else {
- Dynamic<T> dynamic = new Dynamic(dynamicops, t0);
-@@ -135,7 +121,7 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
- }, dynamic2);
- });
-
-- this.c.put(i1, optional);
-+ if (optional.isPresent()) this.c.put(i1, optional); // Paper - NO!!!
- optional.ifPresent((minecraftserializable) -> {
- this.b(i1);
- if (flag) {
-@@ -199,7 +185,7 @@ public class RegionFileSection<R extends MinecraftSerializable> extends RegionFi
- if (optional != null && optional.isPresent()) {
- this.d.add(i);
- } else {
-- RegionFileSection.LOGGER.warn("No data for position: {}", SectionPosition.a(i));
-+ //RegionFileSection.LOGGER.warn("No data for position: {}", SectionPosition.a(i)); // Paper - hush
- }
- }
-
-diff --git a/src/main/java/net/minecraft/server/VillagePlaceRecord.java b/src/main/java/net/minecraft/server/VillagePlaceRecord.java
-index 1e9d7a3f902eb4571b93bb0e58cba966365f07b8..44535a3e2c3320aac472c5a7ee557fac7bab2530 100644
---- a/src/main/java/net/minecraft/server/VillagePlaceRecord.java
-+++ b/src/main/java/net/minecraft/server/VillagePlaceRecord.java
-@@ -32,6 +32,7 @@ public class VillagePlaceRecord implements MinecraftSerializable {
- return dynamicops.createMap(ImmutableMap.of(dynamicops.createString("pos"), this.a.a(dynamicops), dynamicops.createString("type"), dynamicops.createString(IRegistry.POINT_OF_INTEREST_TYPE.getKey(this.b).toString()), dynamicops.createString("free_tickets"), dynamicops.createInt(this.c)));
- }
-
-+ protected final boolean decreaseVacancy() { return b(); } // Paper - OBFHELPER
- protected boolean b() {
- if (this.c <= 0) {
- return false;
-@@ -42,6 +43,7 @@ public class VillagePlaceRecord implements MinecraftSerializable {
- }
- }
-
-+ protected final boolean increaseVacancy() { return c(); } // Paper - OBFHELPER
- protected boolean c() {
- if (this.c >= this.b.b()) {
- return false;
-@@ -52,14 +54,17 @@ public class VillagePlaceRecord implements MinecraftSerializable {
- }
- }
-
-+ public final boolean hasVacancy() { return d(); } // Paper - OBFHELPER
- public boolean d() {
- return this.c > 0;
- }
-
-+ public final boolean isOccupied() { return e(); } // Paper - OBFHELPER
- public boolean e() {
- return this.c != this.b.b();
- }
-
-+ public final BlockPosition getPosition() { return f(); } // Paper
- public BlockPosition f() {
- return this.a;
- }
-diff --git a/src/main/java/net/minecraft/server/VillagePlaceSection.java b/src/main/java/net/minecraft/server/VillagePlaceSection.java
-index 3f2602dbe0995f8d01d4a1428d919405d711a205..436b064c3b277143075386fc9a71027fb5962681 100644
---- a/src/main/java/net/minecraft/server/VillagePlaceSection.java
-+++ b/src/main/java/net/minecraft/server/VillagePlaceSection.java
-@@ -23,7 +23,7 @@ public class VillagePlaceSection implements MinecraftSerializable {
-
- private static final Logger LOGGER = LogManager.getLogger();
- private final Short2ObjectMap<VillagePlaceRecord> b = new Short2ObjectOpenHashMap();
-- private final Map<VillagePlaceType, Set<VillagePlaceRecord>> c = Maps.newHashMap();
-+ private final Map<VillagePlaceType, Set<VillagePlaceRecord>> c = Maps.newHashMap(); public final Map<VillagePlaceType, Set<VillagePlaceRecord>> getRecords() { return c; } // Paper - OBFHELPER
- private final Runnable d;
- private boolean e;
-
-diff --git a/src/main/java/net/minecraft/server/VillagePlaceType.java b/src/main/java/net/minecraft/server/VillagePlaceType.java
-index ab3e054cd2f38756a5d802d4d981022318ab047d..c1f293fc98d3efb4665cfb9036f208b842fc8e36 100644
---- a/src/main/java/net/minecraft/server/VillagePlaceType.java
-+++ b/src/main/java/net/minecraft/server/VillagePlaceType.java
-@@ -12,8 +12,14 @@ import java.util.stream.Stream;
-
- public class VillagePlaceType {
-
-+ static Set<VillagePlaceType> professionCache; // Paper
- private static final Predicate<VillagePlaceType> v = (villageplacetype) -> {
-- return ((Set) IRegistry.VILLAGER_PROFESSION.d().map(VillagerProfession::b).collect(Collectors.toSet())).contains(villageplacetype);
-+ // Paper start
-+ if (professionCache == null) {
-+ professionCache = IRegistry.VILLAGER_PROFESSION.d().map(VillagerProfession::b).collect(Collectors.toSet());
-+ }
-+ return professionCache.contains(villageplacetype);
-+ // Paper end
- };
- public static final Predicate<VillagePlaceType> a = (villageplacetype) -> {
- return true;
-@@ -89,11 +95,11 @@ public class VillagePlaceType {
- }
-
- private static VillagePlaceType a(String s, Set<IBlockData> set, int i, int j) {
-- return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (Object) (new VillagePlaceType(s, set, i, j))));
-+ return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (new VillagePlaceType(s, set, i, j)))); // Paper - decompile error
- }
-
- private static VillagePlaceType a(String s, Set<IBlockData> set, int i, Predicate<VillagePlaceType> predicate, int j) {
-- return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (Object) (new VillagePlaceType(s, set, i, predicate, j))));
-+ return a((VillagePlaceType) IRegistry.POINT_OF_INTEREST_TYPE.a(new MinecraftKey(s), (new VillagePlaceType(s, set, i, predicate, j)))); // Paper - decompile error
- }
-
- private static VillagePlaceType a(VillagePlaceType villageplacetype) {
-diff --git a/src/main/java/net/minecraft/server/VillagerProfession.java b/src/main/java/net/minecraft/server/VillagerProfession.java
-index c38296165b33698bc15fe49a2de0d0d19cfb910a..f9d7a16c79a4e3ffe8b6e7ed469236a93892f01d 100644
---- a/src/main/java/net/minecraft/server/VillagerProfession.java
-+++ b/src/main/java/net/minecraft/server/VillagerProfession.java
-@@ -61,6 +61,7 @@ public class VillagerProfession {
- }
-
- static VillagerProfession a(String s, VillagePlaceType villageplacetype, ImmutableSet<Item> immutableset, ImmutableSet<Block> immutableset1, @Nullable SoundEffect soundeffect) {
-+ VillagePlaceType.professionCache = null; // Paper
- return (VillagerProfession) IRegistry.a((IRegistry) IRegistry.VILLAGER_PROFESSION, new MinecraftKey(s), (Object) (new VillagerProfession(s, villageplacetype, immutableset, immutableset1, soundeffect)));
- }
- }
diff --git a/patches/removed/1.16/No longer Needed/0276-Send-nearby-packets-from-world-player-list-not-serve.patch b/patches/removed/1.16/No longer Needed/0276-Send-nearby-packets-from-world-player-list-not-serve.patch
deleted file mode 100644
index 4d4976d14d..0000000000
--- a/patches/removed/1.16/No longer Needed/0276-Send-nearby-packets-from-world-player-list-not-serve.patch
+++ /dev/null
@@ -1,82 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Mystiflow <[email protected]>
-Date: Fri, 6 Jul 2018 13:21:30 +0100
-Subject: [PATCH] Send nearby packets from world player list not server list
-
-
-diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java
-index 939cec9121c051c5459084e4078740a7607803f3..917ea676d9ce2ea0b10e3a75b7f35f011c3599f6 100644
---- a/src/main/java/net/minecraft/server/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/PlayerList.java
-@@ -910,8 +910,25 @@ public abstract class PlayerList {
- }
-
- public void sendPacketNearby(@Nullable EntityHuman entityhuman, double d0, double d1, double d2, double d3, DimensionManager dimensionmanager, Packet<?> packet) {
-- for (int i = 0; i < this.players.size(); ++i) {
-- EntityPlayer entityplayer = (EntityPlayer) this.players.get(i);
-+ // Paper start - Use world list instead of server list where preferable
-+ sendPacketNearby(entityhuman, d0, d1, d2, d3, dimensionmanager, null, packet); // Retained for compatibility
-+ }
-+
-+ public void sendPacketNearby(@Nullable EntityHuman entityhuman, double d0, double d1, double d2, double d3, WorldServer world, Packet<?> packet) {
-+ sendPacketNearby(entityhuman, d0, d1, d2, d3, world.worldProvider.getDimensionManager(), world, packet);
-+ }
-+
-+ public void sendPacketNearby(@Nullable EntityHuman entityhuman, double d0, double d1, double d2, double d3, DimensionManager dimensionmanager, @Nullable WorldServer world, Packet<?> packet) {
-+ if (world == null && entityhuman != null && entityhuman.world instanceof WorldServer) {
-+ world = (WorldServer) entityhuman.world;
-+ }
-+
-+ List<? extends EntityHuman> players1 = world == null ? players : world.players;
-+ for (int j = 0; j < players1.size(); ++j) {
-+ EntityHuman entity = players1.get(j);
-+ if (!(entity instanceof EntityPlayer)) continue;
-+ EntityPlayer entityplayer = (EntityPlayer) entity;
-+ // Paper end
-
- // CraftBukkit start - Test if player receiving packet can see the source of the packet
- if (entityhuman != null && entityhuman instanceof EntityPlayer && !entityplayer.getBukkitEntity().canSee(((EntityPlayer) entityhuman).getBukkitEntity())) {
-@@ -919,7 +936,7 @@ public abstract class PlayerList {
- }
- // CraftBukkit end
-
-- if (entityplayer != entityhuman && entityplayer.dimension == dimensionmanager) {
-+ if (entityplayer != entityhuman && (world != null || entityplayer.dimension == dimensionmanager)) { // Paper
- double d4 = d0 - entityplayer.locX();
- double d5 = d1 - entityplayer.locY();
- double d6 = d2 - entityplayer.locZ();
-diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
-index 3067ab76d94c58fbfd52fac6754bf6d6d7f01d09..6e878c9b9dee511812df5ea2491d953f677c3f58 100644
---- a/src/main/java/net/minecraft/server/WorldServer.java
-+++ b/src/main/java/net/minecraft/server/WorldServer.java
-@@ -1276,7 +1276,7 @@ public class WorldServer extends World {
- }
- // CraftBukkit end
- this.globalEntityList.add(entitylightning);
-- this.server.getPlayerList().sendPacketNearby((EntityHuman) null, entitylightning.locX(), entitylightning.locY(), entitylightning.locZ(), 512.0D, this.worldProvider.getDimensionManager(), new PacketPlayOutSpawnEntityWeather(entitylightning));
-+ this.server.getPlayerList().sendPacketNearby((EntityHuman) null, entitylightning.locX(), entitylightning.locY(), entitylightning.locZ(), 512.0D, this, new PacketPlayOutSpawnEntityWeather(entitylightning)); // Paper - use world instead of dimension
- }
-
- @Override
-@@ -1408,7 +1408,7 @@ public class WorldServer extends World {
- BlockActionData blockactiondata = (BlockActionData) this.I.removeFirst();
-
- if (this.a(blockactiondata)) {
-- this.server.getPlayerList().sendPacketNearby((EntityHuman) null, (double) blockactiondata.a().getX(), (double) blockactiondata.a().getY(), (double) blockactiondata.a().getZ(), 64.0D, this.worldProvider.getDimensionManager(), new PacketPlayOutBlockAction(blockactiondata.a(), blockactiondata.b(), blockactiondata.c(), blockactiondata.d()));
-+ this.server.getPlayerList().sendPacketNearby((EntityHuman) null, (double) blockactiondata.a().getX(), (double) blockactiondata.a().getY(), (double) blockactiondata.a().getZ(), 64.0D, this, new PacketPlayOutBlockAction(blockactiondata.a(), blockactiondata.b(), blockactiondata.c(), blockactiondata.d()));
- }
- }
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 6c3cd51d92d2271cd216c42c18733d549fbe668d..ad951812835b1fa786e964c533efc4547c57b7a2 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -2122,7 +2122,7 @@ public class CraftWorld implements World {
- double z = loc.getZ();
-
- PacketPlayOutCustomSoundEffect packet = new PacketPlayOutCustomSoundEffect(new MinecraftKey(sound), SoundCategory.valueOf(category.name()), new Vec3D(x, y, z), volume, pitch);
-- world.getMinecraftServer().getPlayerList().sendPacketNearby(null, x, y, z, volume > 1.0F ? 16.0F * volume : 16.0D, this.world.getWorldProvider().getDimensionManager(), packet);
-+ world.getMinecraftServer().getPlayerList().sendPacketNearby(null, x, y, z, volume > 1.0F ? 16.0F * volume : 16.0D, this.world, packet); // Paper - this.world.dimension -> this.world
- }
-
- private static Map<String, GameRules.GameRuleKey<?>> gamerules;
diff --git a/patches/removed/1.16/No longer Needed/0302-Avoid-dimension-id-collisions.patch b/patches/removed/1.16/No longer Needed/0302-Avoid-dimension-id-collisions.patch
deleted file mode 100644
index 1c32c41eb7..0000000000
--- a/patches/removed/1.16/No longer Needed/0302-Avoid-dimension-id-collisions.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Brokkonaut <[email protected]>
-Date: Tue, 25 Sep 2018 06:53:43 +0200
-Subject: [PATCH] Avoid dimension id collisions
-
-getDimensionId() returns the dimension id - 1. So without this patch
-we would reuse an existing dimension id, if some other dimension was
-unloaded before.
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index 011d0927da7a2a67dcd6d75e3af07d38f30acf81..3c43f318c4cff914128e2f7060516ce7ebb6e1c9 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-@@ -1002,7 +1002,7 @@ public final class CraftServer implements Server {
- boolean used = false;
- do {
- for (WorldServer server : console.getWorlds()) {
-- used = server.getWorldProvider().getDimensionManager().getDimensionID() == dimension;
-+ used = server.getWorldProvider().getDimensionManager().getDimensionID() + 1 == dimension; // Paper - getDimensionID returns the dimension - 1, so we have to add 1
- if (used) {
- dimension++;
- break;
diff --git a/patches/removed/1.16/No longer Needed/0316-Fix-MC-93764.patch b/patches/removed/1.16/No longer Needed/0316-Fix-MC-93764.patch
deleted file mode 100644
index decbb2303f..0000000000
--- a/patches/removed/1.16/No longer Needed/0316-Fix-MC-93764.patch
+++ /dev/null
@@ -1,19 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: BillyGalbreath <[email protected]>
-Date: Fri, 19 Oct 2018 19:38:45 -0500
-Subject: [PATCH] Fix MC-93764
-
-
-diff --git a/src/main/java/net/minecraft/server/WorldProviderTheEnd.java b/src/main/java/net/minecraft/server/WorldProviderTheEnd.java
-index 9d4fcf8bcfdc5c09fe0a7ba18a229be3b0e7115c..4b9760709df89ab8378184cb643a9079685b6230 100644
---- a/src/main/java/net/minecraft/server/WorldProviderTheEnd.java
-+++ b/src/main/java/net/minecraft/server/WorldProviderTheEnd.java
-@@ -27,7 +27,7 @@ public class WorldProviderTheEnd extends WorldProvider {
-
- @Override
- public float a(long i, float f) {
-- return 0.0F;
-+ return 0.5F; // Paper - fix MC-93764
- }
-
- @Override
diff --git a/patches/removed/1.16/No longer Needed/0373-Fix-some-generation-concurrency-issues.patch b/patches/removed/1.16/No longer Needed/0373-Fix-some-generation-concurrency-issues.patch
deleted file mode 100644
index 016b1f23c8..0000000000
--- a/patches/removed/1.16/No longer Needed/0373-Fix-some-generation-concurrency-issues.patch
+++ /dev/null
@@ -1,223 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Fri, 24 May 2019 07:53:16 +0100
-Subject: [PATCH] Fix some generation concurrency issues
-
-
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index a59a965104a2c2977fa3b3d0a199913df268bbd3..69db339c29c8f06026f05b0b5bb8019099af3fdf 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -86,6 +86,23 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
- private int tileTickPosition;
- public final Map<Explosion.CacheKey, Float> explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions
- public java.util.ArrayDeque<BlockRedstoneTorch.RedstoneUpdateInfo> redstoneUpdateInfos; // Paper - Move from Map in BlockRedstoneTorch to here
-+ // Paper start - yes this is hacky as shit
-+ RegionLimitedWorldAccess regionLimited;
-+ World originalWorld;
-+ public World regionLimited(RegionLimitedWorldAccess limitedWorldAccess) {
-+ try {
-+ World clone = (World) super.clone();
-+ clone.regionLimited = limitedWorldAccess;
-+ clone.originalWorld = this;
-+ return clone;
-+ } catch (CloneNotSupportedException e1) {
-+ }
-+ return null;
-+ }
-+ ChunkCoordIntPair[] strongholdCoords;
-+ List<StructureStart> strongholdStuctures = Lists.newArrayList();
-+ final java.lang.Object stuctureLock = new Object();
-+ // Paper end
-
- public CraftWorld getWorld() {
- return this.world;
-diff --git a/src/main/java/net/minecraft/server/WorldGenFeatureStateProviderWeighted.java b/src/main/java/net/minecraft/server/WorldGenFeatureStateProviderWeighted.java
-index 22e14fe1e98c8439f8db74c9464137a497fdaf7c..e2af6d43b2eafeecad8fd070fc70195c7b0bb93f 100644
---- a/src/main/java/net/minecraft/server/WorldGenFeatureStateProviderWeighted.java
-+++ b/src/main/java/net/minecraft/server/WorldGenFeatureStateProviderWeighted.java
-@@ -23,18 +23,18 @@ public class WorldGenFeatureStateProviderWeighted extends WorldGenFeatureStatePr
- this(new WeightedList<>(dynamic.get("entries").orElseEmptyList(), IBlockData::a));
- }
-
-- public WorldGenFeatureStateProviderWeighted a(IBlockData iblockdata, int i) {
-+ public synchronized WorldGenFeatureStateProviderWeighted a(IBlockData iblockdata, int i) { // Paper
- this.b.a(iblockdata, i);
- return this;
- }
-
- @Override
-- public IBlockData a(Random random, BlockPosition blockposition) {
-+ public synchronized IBlockData a(Random random, BlockPosition blockposition) { // Paper
- return (IBlockData) this.b.b(random);
- }
-
- @Override
-- public <T> T a(DynamicOps<T> dynamicops) {
-+ public synchronized <T> T a(DynamicOps<T> dynamicops) { // Paper
- Builder<T, T> builder = ImmutableMap.builder();
-
- builder.put(dynamicops.createString("type"), dynamicops.createString(IRegistry.t.getKey(this.a).toString())).put(dynamicops.createString("entries"), this.b.a(dynamicops, (iblockdata) -> {
-diff --git a/src/main/java/net/minecraft/server/WorldGenStronghold.java b/src/main/java/net/minecraft/server/WorldGenStronghold.java
-index fc4348b60242e4a9d8612c3b8ce01711c32f4b1c..44be7169ffd5961df28d21a319d2cc7569662baf 100644
---- a/src/main/java/net/minecraft/server/WorldGenStronghold.java
-+++ b/src/main/java/net/minecraft/server/WorldGenStronghold.java
-@@ -10,10 +10,12 @@ import javax.annotation.Nullable;
-
- public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureEmptyConfiguration> {
-
-+ /* // Paper start - no shared state
- private boolean a;
- private ChunkCoordIntPair[] aq;
- private final List<StructureStart> ar = Lists.newArrayList();
- private long as;
-+ */
-
- public WorldGenStronghold(Function<Dynamic<?>, ? extends WorldGenFeatureEmptyConfiguration> function) {
- super(function);
-@@ -21,16 +23,22 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureEmptyC
-
- @Override
- public boolean a(BiomeManager biomemanager, ChunkGenerator<?> chunkgenerator, Random random, int i, int j, BiomeBase biomebase) {
-+ // Paper start
-+ /*
- if (this.as != chunkgenerator.getSeed()) {
- this.d();
- }
-+ */
-+ final World world = chunkgenerator.getWorld();
-
-- if (!this.a) {
-+ synchronized (world.stuctureLock) {
-+ if ( world.strongholdCoords == null) {
- this.a(chunkgenerator);
-- this.a = true;
-- }
-+ // this.a = true;
-+ }}
-+ // Paper end
-
-- ChunkCoordIntPair[] achunkcoordintpair = this.aq;
-+ ChunkCoordIntPair[] achunkcoordintpair = world.strongholdCoords; // Paper
- int k = achunkcoordintpair.length;
-
- for (int l = 0; l < k; ++l) {
-@@ -45,9 +53,11 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureEmptyC
- }
-
- private void d() {
-+ /* // Paper
- this.a = false;
- this.aq = null;
- this.ar.clear();
-+ */ // Paper
- }
-
- @Override
-@@ -65,25 +75,32 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureEmptyC
- return 8;
- }
-
-+
- @Nullable
- @Override
- public synchronized BlockPosition getNearestGeneratedFeature(World world, ChunkGenerator<? extends GeneratorSettingsDefault> chunkgenerator, BlockPosition blockposition, int i, boolean flag) {
- if (!chunkgenerator.getWorldChunkManager().a(this)) {
- return null;
- } else {
-+ // Paper start - no shared state
-+ /*
- if (this.as != world.getSeed()) {
- this.d();
- }
-+ */
-
-- if (!this.a) {
-- this.a(chunkgenerator);
-- this.a = true;
-+ synchronized (world.stuctureLock) {
-+ if ( world.strongholdCoords == null) {
-+ this.a(chunkgenerator);
-+ //this.a = true;
-+ }
- }
-+ // Paper end
-
- BlockPosition blockposition1 = null;
- BlockPosition.MutableBlockPosition blockposition_mutableblockposition = new BlockPosition.MutableBlockPosition();
- double d0 = Double.MAX_VALUE;
-- ChunkCoordIntPair[] achunkcoordintpair = this.aq;
-+ ChunkCoordIntPair[] achunkcoordintpair = world.strongholdCoords; // Paper
- int j = achunkcoordintpair.length;
-
- for (int k = 0; k < j; ++k) {
-@@ -106,7 +123,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureEmptyC
- }
-
- private void a(ChunkGenerator<?> chunkgenerator) {
-- this.as = chunkgenerator.getSeed();
-+ //this.as = chunkgenerator.getSeed(); // Paper
- List<BiomeBase> list = Lists.newArrayList();
- Iterator iterator = IRegistry.BIOME.iterator();
-
-@@ -122,15 +139,15 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureEmptyC
- int j = chunkgenerator.getSettings().f();
- int k = chunkgenerator.getSettings().g();
-
-- this.aq = new ChunkCoordIntPair[j];
-+ ChunkCoordIntPair[] strongholdCoords = chunkgenerator.getWorld().strongholdCoords = new ChunkCoordIntPair[j]; // Paper
- int l = 0;
-- Iterator iterator1 = this.ar.iterator();
-+ Iterator iterator1 = chunkgenerator.getWorld().strongholdStuctures.iterator(); // Paper
-
- while (iterator1.hasNext()) {
- StructureStart structurestart = (StructureStart) iterator1.next();
-
-- if (l < this.aq.length) {
-- this.aq[l++] = new ChunkCoordIntPair(structurestart.f(), structurestart.g());
-+ if (l < strongholdCoords.length) { // Paper
-+ strongholdCoords[l++] = new ChunkCoordIntPair(structurestart.f(), structurestart.g()); // Paper
- }
- }
-
-@@ -140,11 +157,11 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureEmptyC
- double d0 = random.nextDouble() * 3.141592653589793D * 2.0D;
- int i1 = l;
-
-- if (l < this.aq.length) {
-+ if (l < strongholdCoords.length) { // Paper
- int j1 = 0;
- int k1 = 0;
-
-- for (int l1 = 0; l1 < this.aq.length; ++l1) {
-+ for (int l1 = 0; l1 < strongholdCoords.length; ++l1) { // Paper
- double d1 = (double) (4 * i + i * k1 * 6) + (random.nextDouble() - 0.5D) * (double) i * 2.5D;
- int i2 = (int) Math.round(Math.cos(d0) * d1);
- int j2 = (int) Math.round(Math.sin(d0) * d1);
-@@ -156,7 +173,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureEmptyC
- }
-
- if (l1 >= i1) {
-- this.aq[l1] = new ChunkCoordIntPair(i2, j2);
-+ strongholdCoords[l1] = new ChunkCoordIntPair(i2, j2); // Paper
- }
-
- d0 += 6.283185307179586D / (double) k;
-@@ -165,7 +182,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureEmptyC
- ++k1;
- j1 = 0;
- k += 2 * k / (k1 + 1);
-- k = Math.min(k, this.aq.length - l1);
-+ k = Math.min(k, strongholdCoords.length - l1); // Paper
- d0 += random.nextDouble() * 3.141592653589793D * 2.0D;
- }
- }
-@@ -207,7 +224,7 @@ public class WorldGenStronghold extends StructureGenerator<WorldGenFeatureEmptyC
- this.a(chunkgenerator.getSeaLevel(), this.d, 10);
- } while (this.b.isEmpty() || worldgenstrongholdpieces_worldgenstrongholdstart.b == null);
-
-- ((WorldGenStronghold) this.l()).ar.add(this);
-+ chunkgenerator.getWorld().strongholdStuctures.add(this); // Paper - this worries me, this is never cleared, even in vanilla (world seed never changes "world", and that appears to be the only relevant world)
- }
- }
- }
diff --git a/patches/removed/1.16/No longer Needed/0401-Fix-zero-tick-instant-grow-farms-MC-113809.patch b/patches/removed/1.16/No longer Needed/0401-Fix-zero-tick-instant-grow-farms-MC-113809.patch
deleted file mode 100644
index 4f8bf4281d..0000000000
--- a/patches/removed/1.16/No longer Needed/0401-Fix-zero-tick-instant-grow-farms-MC-113809.patch
+++ /dev/null
@@ -1,96 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Phoenix616 <[email protected]>
-Date: Sun, 15 Sep 2019 11:32:32 -0500
-Subject: [PATCH] Fix zero-tick instant grow farms MC-113809
-
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index 62fe175dc4f00cc9cab6cbd828b57e25740b3793..f6f5f9dea6284582e9a175c0875273ee1db76076 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -552,6 +552,11 @@ public class PaperWorldConfig {
- disableRelativeProjectileVelocity = getBoolean("game-mechanics.disable-relative-projectile-velocity", false);
- }
-
-+ public boolean fixZeroTickInstantGrowFarms = true;
-+ private void fixZeroTickInstantGrowFarms() {
-+ fixZeroTickInstantGrowFarms = getBoolean("fix-zero-tick-instant-grow-farms", fixZeroTickInstantGrowFarms);
-+ }
-+
- public boolean altItemDespawnRateEnabled;
- public Map<Material, Integer> altItemDespawnRateMap;
- private void altItemDespawnRate() {
-diff --git a/src/main/java/net/minecraft/server/Block.java b/src/main/java/net/minecraft/server/Block.java
-index 540fcce1dd4d64dee51e2594f2199fac5299c6a0..e29ec958b3519d92cda215a50e97e6852d71c684 100644
---- a/src/main/java/net/minecraft/server/Block.java
-+++ b/src/main/java/net/minecraft/server/Block.java
-@@ -46,6 +46,7 @@ public class Block implements IMaterial {
- private final float g;
- protected final BlockStateList<Block, IBlockData> blockStateList;
- private IBlockData blockData;
-+ public boolean randomTick = false; // Paper - fix MC-113809
- protected final boolean v;
- private final boolean i;
- private final boolean j;
-diff --git a/src/main/java/net/minecraft/server/BlockBamboo.java b/src/main/java/net/minecraft/server/BlockBamboo.java
-index c482aad3e3e255dfe13b622859ed61b780a9e08e..02c548dd9c9a97bfb55d39ba2f6d4ab85ada0573 100644
---- a/src/main/java/net/minecraft/server/BlockBamboo.java
-+++ b/src/main/java/net/minecraft/server/BlockBamboo.java
-@@ -85,6 +85,7 @@ public class BlockBamboo extends Block implements IBlockFragilePlantElement {
- if (!iblockdata.canPlace(worldserver, blockposition)) {
- worldserver.b(blockposition, true);
- } else if ((Integer) iblockdata.get(BlockBamboo.f) == 0) {
-+ if (worldserver.paperConfig.fixZeroTickInstantGrowFarms && !randomTick) return; // Paper - fix MC-113809
- if (random.nextInt(Math.max(1, (int) (100.0F / worldserver.spigotConfig.bambooModifier) * 3)) == 0 && worldserver.isEmpty(blockposition.up()) && worldserver.getLightLevel(blockposition.up(), 0) >= 9) { // Spigot
- int i = this.b(worldserver, blockposition) + 1;
-
-diff --git a/src/main/java/net/minecraft/server/BlockCactus.java b/src/main/java/net/minecraft/server/BlockCactus.java
-index e0974e256f0f10e047b9eb8e362982c6578d2d98..3524fcb927865d7b8754d9fbf85b853f09b94bb8 100644
---- a/src/main/java/net/minecraft/server/BlockCactus.java
-+++ b/src/main/java/net/minecraft/server/BlockCactus.java
-@@ -21,6 +21,7 @@ public class BlockCactus extends Block {
- if (!iblockdata.canPlace(worldserver, blockposition)) {
- worldserver.b(blockposition, true);
- } else {
-+ if (worldserver.paperConfig.fixZeroTickInstantGrowFarms && !randomTick) return; // Paper - fix MC-113809
- BlockPosition blockposition1 = blockposition.up();
-
- if (worldserver.isEmpty(blockposition1)) {
-diff --git a/src/main/java/net/minecraft/server/BlockChorusFlower.java b/src/main/java/net/minecraft/server/BlockChorusFlower.java
-index d70b52cadf1b76eff7984127837b0a3aa36f6a0e..b624cf38047e242569d30ee4e3ad971455b5ff0a 100644
---- a/src/main/java/net/minecraft/server/BlockChorusFlower.java
-+++ b/src/main/java/net/minecraft/server/BlockChorusFlower.java
-@@ -22,6 +22,7 @@ public class BlockChorusFlower extends Block {
- if (!iblockdata.canPlace(worldserver, blockposition)) {
- worldserver.b(blockposition, true);
- } else {
-+ if (worldserver.paperConfig.fixZeroTickInstantGrowFarms && !randomTick) return; // Paper - fix MC-113809
- BlockPosition blockposition1 = blockposition.up();
-
- if (worldserver.isEmpty(blockposition1) && blockposition1.getY() < 256) {
-diff --git a/src/main/java/net/minecraft/server/BlockReed.java b/src/main/java/net/minecraft/server/BlockReed.java
-index 55b07444e1d769952f2a411b1b5d1032565af8a1..3bc3c5aa29f45cd2ee1c0401b1ee1b1d49e81926 100644
---- a/src/main/java/net/minecraft/server/BlockReed.java
-+++ b/src/main/java/net/minecraft/server/BlockReed.java
-@@ -23,6 +23,7 @@ public class BlockReed extends Block {
- if (!iblockdata.canPlace(worldserver, blockposition)) {
- worldserver.b(blockposition, true);
- } else if (worldserver.isEmpty(blockposition.up())) {
-+ if (worldserver.paperConfig.fixZeroTickInstantGrowFarms && !randomTick) return; // Paper - fix MC-113809
- int i;
-
- for (i = 1; worldserver.getType(blockposition.down(i)).getBlock() == this; ++i) {
-diff --git a/src/main/java/net/minecraft/server/WorldServer.java b/src/main/java/net/minecraft/server/WorldServer.java
-index 80e2a15bebe93939dc7b43b17b8116965438c062..3ecc73d021c09fbcad74dd62aced460771f86038 100644
---- a/src/main/java/net/minecraft/server/WorldServer.java
-+++ b/src/main/java/net/minecraft/server/WorldServer.java
-@@ -589,7 +589,9 @@ public class WorldServer extends World {
- IBlockData iblockdata = chunksection.getType(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k);
-
- if (iblockdata.q()) {
-+ iblockdata.getBlock().randomTick = true; // Paper - fix MC-113809
- iblockdata.b(this, blockposition2, this.random);
-+ iblockdata.getBlock().randomTick = false; // Paper - fix MC-113809
- }
-
- Fluid fluid = iblockdata.getFluid();
diff --git a/patches/removed/1.16/No longer Needed/0412-Fix-spawn-radius-being-treated-as-0.patch b/patches/removed/1.16/No longer Needed/0412-Fix-spawn-radius-being-treated-as-0.patch
deleted file mode 100644
index 7f6c706c69..0000000000
--- a/patches/removed/1.16/No longer Needed/0412-Fix-spawn-radius-being-treated-as-0.patch
+++ /dev/null
@@ -1,21 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Sun, 15 Dec 2019 19:41:28 +0000
-Subject: [PATCH] Fix spawn radius being treated as 0
-
-
-Not needed anymore?
-
-diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java
-index 81f00141776a1767b907d14ef04f60b576110128..86bbbbaefca9ace5327d8bc2456939eb9ae8966a 100644
---- a/src/main/java/net/minecraft/server/EntityPlayer.java
-+++ b/src/main/java/net/minecraft/server/EntityPlayer.java
-@@ -129,7 +129,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting {
- public final BlockPosition getSpawnPoint(WorldServer worldserver) {
- BlockPosition blockposition = worldserver.getSpawn();
-
-- if (worldserver.worldProvider.g() && worldserver.getWorldData().getGameType() != EnumGamemode.ADVENTURE) {
-+ if (worldserver.worldProvider.f() && worldserver.getWorldData().getGameType() != EnumGamemode.ADVENTURE) { // Paper
- int i = Math.max(0, this.server.a(worldserver));
- int j = MathHelper.floor(worldserver.getWorldBorder().b((double) blockposition.getX(), (double) blockposition.getZ()));
-
diff --git a/patches/removed/1.16/No longer Needed/0429-Seed-based-feature-search.patch b/patches/removed/1.16/No longer Needed/0429-Seed-based-feature-search.patch
deleted file mode 100644
index 0fa2f297f4..0000000000
--- a/patches/removed/1.16/No longer Needed/0429-Seed-based-feature-search.patch
+++ /dev/null
@@ -1,110 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Phoenix616 <[email protected]>
-Date: Mon, 13 Jan 2020 15:40:32 +0100
-Subject: [PATCH] Seed based feature search
-
-This fixes the issue where the server will load surrounding chunks up to
-a radius of 100 chunks in order to search for features e.g. when running
-the /locate command or for treasure maps (issue #2312).
-This is done by using the same seed checking functionality that is used
-by the server when generating these features before actually attempting
-to load the chunk to check if a feature is available in it.
-
-The only downside of this is that it breaks once the seed or generator
-changes but this should usually not happen. A config option to disable
-this improvement is added though in case that should ever be necessary.
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index cab503bd5c34d12b38a2f5deed6d3feb9287b370..2ab810f71beaa608af2194165696817a2cde4b92 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -357,6 +357,12 @@ public class PaperWorldConfig {
- }
- }
-
-+ public boolean seedBasedFeatureSearch = true;
-+ private void seedBasedFeatureSearch() {
-+ seedBasedFeatureSearch = getBoolean("seed-based-feature-search", seedBasedFeatureSearch);
-+ log("Feature search is based on seed: " + seedBasedFeatureSearch);
-+ }
-+
- public int maxCollisionsPerEntity;
- private void maxEntityCollision() {
- maxCollisionsPerEntity = getInt( "max-entity-collisions", this.spigotConfig.getInt("max-entity-collisions", 8) );
-diff --git a/src/main/java/net/minecraft/server/BiomeManager.java b/src/main/java/net/minecraft/server/BiomeManager.java
-index e96f544f126371f6f629a20ba3c99ba42d31e04a..68423645df3aa08d4c5126ff068d5e566927f744 100644
---- a/src/main/java/net/minecraft/server/BiomeManager.java
-+++ b/src/main/java/net/minecraft/server/BiomeManager.java
-@@ -12,10 +12,12 @@ public class BiomeManager {
- this.c = genlayerzoomer;
- }
-
-+ public BiomeManager withProvider(WorldChunkManager worldchunkmanager) { return a(worldchunkmanager); } // Paper - OBFHELPER
- public BiomeManager a(WorldChunkManager worldchunkmanager) {
- return new BiomeManager(worldchunkmanager, this.b, this.c);
- }
-
-+ public BiomeBase getBiome(BlockPosition blockposition) { return a(blockposition); } // Paper - OBFHELPER
- public BiomeBase a(BlockPosition blockposition) {
- return this.c.a(this.b, blockposition.getX(), blockposition.getY(), blockposition.getZ(), this.a);
- }
-diff --git a/src/main/java/net/minecraft/server/ChunkCoordIntPair.java b/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
-index 5a975f6bc60922ac872ec9c00c9150ce7dcad046..f617636a22167b06ac8073aa25efd8c7099155f0 100644
---- a/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
-+++ b/src/main/java/net/minecraft/server/ChunkCoordIntPair.java
-@@ -68,10 +68,12 @@ public class ChunkCoordIntPair {
- }
- }
-
-+ public int getBlockX() { return d(); } // Paper - OBFHELPER
- public int d() {
- return this.x << 4;
- }
-
-+ public int getBlockZ() { return e(); } // Paper - OBFHELPER
- public int e() {
- return this.z << 4;
- }
-diff --git a/src/main/java/net/minecraft/server/StructureGenerator.java b/src/main/java/net/minecraft/server/StructureGenerator.java
-index e8ce2ecf23e58d82febf6b9441e0004e69cdc858..acfe732af5b9f63fc2f6b78499defabe2e73ee45 100644
---- a/src/main/java/net/minecraft/server/StructureGenerator.java
-+++ b/src/main/java/net/minecraft/server/StructureGenerator.java
-@@ -109,6 +109,15 @@ public abstract class StructureGenerator<C extends WorldGenFeatureConfiguration>
- if (flag1 || flag2) {
- ChunkCoordIntPair chunkcoordintpair = this.a(chunkgenerator, seededrandom, j, k, i1, j1);
- if (!world.getWorldBorder().isChunkInBounds(chunkcoordintpair.x, chunkcoordintpair.z)) { continue; } // Paper
-+ // Paper start - seed based feature search
-+ if (world.paperConfig.seedBasedFeatureSearch) {
-+ BiomeManager biomeManager = world.getBiomeManager().withProvider(chunkgenerator.getWorldChunkManager());
-+ BiomeBase biomeBase = biomeManager.getBiome(new BlockPosition(chunkcoordintpair.getBlockX() + 9, 0, chunkcoordintpair.getBlockZ() + 9));
-+ if (!shouldGenerate(biomeManager, chunkgenerator, seededrandom, chunkcoordintpair.x, chunkcoordintpair.z, biomeBase)) {
-+ continue;
-+ }
-+ }
-+ // Paper end
- StructureStart structurestart = world.getChunkAt(chunkcoordintpair.x, chunkcoordintpair.z, ChunkStatus.STRUCTURE_STARTS).a(this.b());
-
- if (structurestart != null && structurestart.e()) {
-@@ -165,6 +174,7 @@ public abstract class StructureGenerator<C extends WorldGenFeatureConfiguration>
- return new ChunkCoordIntPair(i + k, j + l);
- }
-
-+ public boolean shouldGenerate(BiomeManager biomemanager, ChunkGenerator<?> chunkgenerator, Random random, int chunkX, int chunkZ, BiomeBase biomebase) { return a(biomemanager, chunkgenerator, random, chunkX, chunkZ, biomebase); } // Paper - OBFHELPER
- public abstract boolean a(BiomeManager biomemanager, ChunkGenerator<?> chunkgenerator, Random random, int i, int j, BiomeBase biomebase);
-
- public abstract StructureGenerator.a a();
-diff --git a/src/main/java/net/minecraft/server/World.java b/src/main/java/net/minecraft/server/World.java
-index 0fa2b335db297c6270090d3dd24ced92eab12825..d7ac4c86d170a8d7d816f86ac691c3b5129a20ba 100644
---- a/src/main/java/net/minecraft/server/World.java
-+++ b/src/main/java/net/minecraft/server/World.java
-@@ -1570,8 +1570,8 @@ public abstract class World implements GeneratorAccess, AutoCloseable {
- return this.methodProfiler;
- }
-
-- @Override
-- public BiomeManager d() {
-+ public BiomeManager getBiomeManager() { return d(); } // Paper - OBFHELPER
-+ @Override public BiomeManager d() {
- return this.biomeManager;
- }
- }
diff --git a/patches/removed/1.16/No longer Needed/0468-Port-20w15a-Villager-AI-optimizations-DROP-1.16.patch b/patches/removed/1.16/No longer Needed/0468-Port-20w15a-Villager-AI-optimizations-DROP-1.16.patch
deleted file mode 100644
index 8b5c367abc..0000000000
--- a/patches/removed/1.16/No longer Needed/0468-Port-20w15a-Villager-AI-optimizations-DROP-1.16.patch
+++ /dev/null
@@ -1,195 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Callahan <[email protected]>
-Date: Wed, 8 Apr 2020 18:00:17 -0500
-Subject: [PATCH] Port 20w15a Villager AI optimizations - DROP 1.16
-
-
-diff --git a/src/main/java/net/minecraft/server/BehaviorController.java b/src/main/java/net/minecraft/server/BehaviorController.java
-index 7c6e687707cdf32638eee41e549818a494cd45ab..396b64ea0fc8a04d9e0aac289033d3d82385b86e 100644
---- a/src/main/java/net/minecraft/server/BehaviorController.java
-+++ b/src/main/java/net/minecraft/server/BehaviorController.java
-@@ -38,30 +38,22 @@ public class BehaviorController<E extends EntityLiving> implements MinecraftSeri
- this.g = Sets.newHashSet();
- this.h = Activity.IDLE;
- this.i = -9999L;
-- collection.forEach((memorymoduletype) -> {
-- Optional optional = (Optional) this.memories.put(memorymoduletype, Optional.empty());
-- });
-- collection1.forEach((sensortype) -> {
-- Sensor sensor = (Sensor) this.sensors.put(sensortype, sensortype.a());
-- });
-- this.sensors.values().forEach((sensor) -> {
-- Iterator iterator = sensor.a().iterator();
--
-- while (iterator.hasNext()) {
-- MemoryModuleType<?> memorymoduletype = (MemoryModuleType) iterator.next();
--
-- this.memories.put(memorymoduletype, Optional.empty());
-+ // Paper start - Port 20w15a pathfinder optimizations
-+ for (final MemoryModuleType<?> memoryModuleType : collection) {
-+ this.memories.put(memoryModuleType, Optional.empty());
-+ }
-+ for (final SensorType<? extends Sensor<? super E>> sensorType : collection1) {
-+ this.sensors.put(sensorType, sensorType.a());
-+ }
-+ for (final Sensor<? super E> sensor : this.sensors.values()) {
-+ for (final MemoryModuleType<?> memoryModuleType : sensor.a()) {
-+ this.memories.put(memoryModuleType, Optional.empty());
- }
--
-- });
-- Iterator iterator = dynamic.get("memories").asMap(Function.identity(), Function.identity()).entrySet().iterator();
--
-- while (iterator.hasNext()) {
-- Entry<Dynamic<T>, Dynamic<T>> entry = (Entry) iterator.next();
--
-- this.a((MemoryModuleType) IRegistry.MEMORY_MODULE_TYPE.get(new MinecraftKey(((Dynamic) entry.getKey()).asString(""))), (Dynamic) entry.getValue());
- }
--
-+ for (final Map.Entry<Dynamic<T>, Dynamic<T>> entry : dynamic.get("memories").asMap(Function.identity(), Function.identity()).entrySet()) {
-+ this.a((MemoryModuleType) IRegistry.MEMORY_MODULE_TYPE.get(new MinecraftKey((entry.getKey()).asString(""))), entry.getValue());
-+ }
-+ // Paper end - Port 20w15a pathfinder optimizations
- }
-
- public boolean hasMemory(MemoryModuleType<?> memorymoduletype) {
-@@ -69,7 +61,7 @@ public class BehaviorController<E extends EntityLiving> implements MinecraftSeri
- }
-
- private <T, U> void a(MemoryModuleType<U> memorymoduletype, Dynamic<T> dynamic) {
-- this.setMemory(memorymoduletype, ((Function) memorymoduletype.getSerializer().orElseThrow(RuntimeException::new)).apply(dynamic));
-+ this.setMemory(memorymoduletype, (memorymoduletype.getSerializer().orElseThrow(RuntimeException::new)).apply(dynamic)); // Paper - decompile fix
- }
-
- public <U> void removeMemory(MemoryModuleType<U> memorymoduletype) {
-@@ -113,13 +105,21 @@ public class BehaviorController<E extends EntityLiving> implements MinecraftSeri
- this.f = set;
- }
-
-+ // Paper start - Port 20w15a pathfinder optimizations
- @Deprecated
-- public Stream<Behavior<? super E>> d() {
-- return this.c.values().stream().flatMap((map) -> {
-- return map.values().stream();
-- }).flatMap(Collection::stream).filter((behavior) -> {
-- return behavior.a() == Behavior.Status.RUNNING;
-- });
-+ public java.util.List<Behavior<? super E>> d() {
-+ final java.util.List<Behavior<? super E>> behaviorList = (java.util.List<Behavior<? super E>>) new it.unimi.dsi.fastutil.objects.ObjectArrayList();
-+ for (final Map<Activity, Set<Behavior<? super E>>> map : this.c.values()) {
-+ for (final Set<Behavior<? super E>> set : map.values()) {
-+ for (final Behavior<? super E> behavior : set) {
-+ if (behavior.a() == Behavior.Status.RUNNING) {
-+ behaviorList.add(behavior);
-+ }
-+ }
-+ }
-+ }
-+ return behaviorList;
-+ // Paper end - Port 20w15a pathfinder optimizations
- }
-
- public void a(Activity activity) {
-@@ -168,12 +168,14 @@ public class BehaviorController<E extends EntityLiving> implements MinecraftSeri
-
- public BehaviorController<E> f() {
- BehaviorController<E> behaviorcontroller = new BehaviorController<>(this.memories.keySet(), this.sensors.keySet(), new Dynamic(DynamicOpsNBT.a, new NBTTagCompound()));
--
-- this.memories.forEach((memorymoduletype, optional) -> {
-- optional.ifPresent((object) -> {
-- Optional optional1 = (Optional) behaviorcontroller.memories.put(memorymoduletype, Optional.of(object));
-- });
-- });
-+ // Paper start - Port 20w15a pathfinder optimizations
-+ for (final Entry<MemoryModuleType<?>, Optional<?>> entry : this.memories.entrySet()) {
-+ final MemoryModuleType<?> memoryModuleType = entry.getKey();
-+ if (entry.getValue().isPresent()) {
-+ behaviorcontroller.memories.put(memoryModuleType, entry.getValue());
-+ }
-+ }
-+ // Paper end - Port 20w15a pathfinder optimizations
- return behaviorcontroller;
- }
-
-@@ -186,14 +188,14 @@ public class BehaviorController<E extends EntityLiving> implements MinecraftSeri
- public void b(WorldServer worldserver, E e0) {
- long i = e0.world.getTime();
-
-- this.d().forEach((behavior) -> {
-+ for(Behavior<? super E> behavior : this.d()) { // Paper - Port 20w15a pathfinder optimizations
- behavior.e(worldserver, e0, i);
-- });
-+ }
- }
-
- @Override
- public <T> T a(DynamicOps<T> dynamicops) {
-- T t0 = dynamicops.createMap((Map) this.memories.entrySet().stream().filter((entry) -> {
-+ T t0 = dynamicops.createMap(this.memories.entrySet().stream().filter((entry) -> { // Paper - decompile fix
- return ((MemoryModuleType) entry.getKey()).getSerializer().isPresent() && ((Optional) entry.getValue()).isPresent();
- }).map((entry) -> {
- return Pair.of(dynamicops.createString(IRegistry.MEMORY_MODULE_TYPE.getKey(entry.getKey()).toString()), ((MinecraftSerializable) ((Optional) entry.getValue()).get()).a(dynamicops));
-@@ -210,33 +212,45 @@ public class BehaviorController<E extends EntityLiving> implements MinecraftSeri
-
- private void d(WorldServer worldserver, E e0) {
- long i = worldserver.getTime();
--
-- this.c.values().stream().flatMap((map) -> {
-- return map.entrySet().stream();
-- }).filter((entry) -> {
-- return this.g.contains(entry.getKey());
-- }).map(Entry::getValue).flatMap(Collection::stream).filter((behavior) -> {
-- return behavior.a() == Behavior.Status.STOPPED;
-- }).forEach((behavior) -> {
-- behavior.b(worldserver, e0, i);
-- });
-+ // Paper start - Port 20w15a pathfinder optimizations
-+ for (final Map<Activity, Set<Behavior<? super E>>> map : this.c.values()) {
-+ for (final Map.Entry<Activity, Set<Behavior<? super E>>> entry : map.entrySet()) {
-+ final Activity activity = entry.getKey();
-+ if (this.g.contains(activity)) {
-+ final Set<Behavior<? super E>> set = entry.getValue();
-+ for (final Behavior<? super E> behavior : set) {
-+ if (behavior.a() == Behavior.Status.STOPPED) {
-+ behavior.b(worldserver, e0, i);
-+ }
-+ }
-+ }
-+ }
-+ }
-+ // Paper end - Port 20w15a pathfinder optimizations
- }
-
- private void e(WorldServer worldserver, E e0) {
- long i = worldserver.getTime();
-
-- this.d().forEach((behavior) -> {
-+ for (final Behavior<? super E> behavior : this.d()) { // Paper - Port 20w15a pathfinder optimizations
- behavior.c(worldserver, e0, i);
-- });
-+ }
- }
-
- private boolean d(Activity activity) {
-- return ((Set) this.e.get(activity)).stream().allMatch((pair) -> {
-- MemoryModuleType<?> memorymoduletype = (MemoryModuleType) pair.getFirst();
-- MemoryStatus memorystatus = (MemoryStatus) pair.getSecond();
--
-- return this.a(memorymoduletype, memorystatus);
-- });
-+ // Paper start - Port 20w15a pathfinder optimizations
-+ if (!this.e.containsKey(activity)) {
-+ return false;
-+ }
-+ for (final Pair<MemoryModuleType<?>, MemoryStatus> pair : this.e.get(activity)) {
-+ MemoryModuleType<?> memorymoduletype = pair.getFirst();
-+ MemoryStatus memorystatus = pair.getSecond();
-+ if (!this.a(memorymoduletype, memorystatus)) {
-+ return false;
-+ }
-+ }
-+ return true;
-+ // Paper end - Port 20w15a pathfinder optimizations
- }
-
- private boolean a(Object object) {
diff --git a/patches/removed/1.17/0009-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch b/patches/removed/1.17/0009-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch
deleted file mode 100644
index 11ed8248cc..0000000000
--- a/patches/removed/1.17/0009-Store-reference-to-current-Chunk-for-Entity-and-Bloc.patch
+++ /dev/null
@@ -1,171 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 4 Jul 2018 02:10:36 -0400
-Subject: [PATCH] Store reference to current Chunk for Entity and Block
- Entities
-
-This enables us a fast reference to the entities current chunk instead
-of having to look it up by hashmap lookups.
-
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 48c9d2b7d56832ebd13749a394b8b715f0b1704d..b633f6b3a36b793e6dbc1b8b554bfba74c719570 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -260,7 +260,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
- }
-
- public boolean isChunkLoaded() {
-- return level.hasChunk((int) Math.floor(this.getX()) >> 4, (int) Math.floor(this.getZ()) >> 4);
-+ return getCurrentChunk() != null;
- }
- // CraftBukkit end
-
-@@ -1762,6 +1762,23 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
- }
-
- // Paper start
-+ public java.lang.ref.WeakReference<net.minecraft.world.level.chunk.LevelChunk> currentChunk = null;
-+
-+ public void setCurrentChunk(net.minecraft.world.level.chunk.LevelChunk chunk) {
-+ this.currentChunk = chunk != null ? new java.lang.ref.WeakReference<>(chunk) : null;
-+ }
-+ /**
-+ * Returns the entities current registered chunk. If the entity is not added to a chunk yet, it will return null
-+ */
-+ public net.minecraft.world.level.chunk.LevelChunk getCurrentChunk() {
-+ final net.minecraft.world.level.chunk.LevelChunk chunk = currentChunk != null ? currentChunk.get() : null;
-+ if (chunk != null && chunk.loaded) {
-+ return chunk;
-+ }
-+
-+ return !inChunk ? null : ((ServerLevel)level).getChunkSource().getChunkAtIfLoadedMainThreadNoCache(xChunk, zChunk);
-+ }
-+
- private ResourceLocation entityKey;
- private String entityKeyString;
-
-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 846fc0f36377337630b2ec2a5f7a5a54c39c2965..bb60c9da9f3ba0d5c5bad22512675ccb841a60e5 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
-@@ -11,6 +11,7 @@ import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.Mirror;
- import net.minecraft.world.level.block.Rotation;
- import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.chunk.LevelChunk;
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
- import org.apache.logging.log4j.util.Supplier;
-@@ -63,6 +64,15 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject {
- getMinecraftKey(); // Try to load if it doesn't exists.
- return tileEntityKeyString;
- }
-+
-+ private java.lang.ref.WeakReference<LevelChunk> currentChunk = null;
-+ public LevelChunk getCurrentChunk() {
-+ final LevelChunk chunk = currentChunk != null ? currentChunk.get() : null;
-+ return chunk != null && chunk.loaded ? chunk : null;
-+ }
-+ public void setCurrentChunk(LevelChunk chunk) {
-+ this.currentChunk = chunk != null ? new java.lang.ref.WeakReference<>(chunk) : null;
-+ }
- // Paper end
-
- @Nullable
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index e2c5a17aa72d1a5412d76881187d4d9ad1763297..ae08fcce66d50d7f61bc3bd4a0e2547d56f53e82 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -89,11 +89,36 @@ public class LevelChunk implements ChunkAccess {
- this(world, pos, biomes, UpgradeData.EMPTY, EmptyTickList.empty(), EmptyTickList.empty(), 0L, (LevelChunkSection[]) null, (Consumer) null);
- }
-
-+ // Paper start
-+ private class TileEntityHashMap extends java.util.HashMap<BlockPos, BlockEntity> {
-+ @Override
-+ public BlockEntity put(BlockPos key, BlockEntity value) {
-+ BlockEntity replaced = super.put(key, value);
-+ if (replaced != null) {
-+ replaced.setCurrentChunk(null);
-+ }
-+ if (value != null) {
-+ value.setCurrentChunk(LevelChunk.this);
-+ }
-+ return replaced;
-+ }
-+
-+ @Override
-+ public BlockEntity remove(Object key) {
-+ BlockEntity removed = super.remove(key);
-+ if (removed != null) {
-+ removed.setCurrentChunk(null);
-+ }
-+ return removed;
-+ }
-+ }
-+ // Paper end
-+
- public LevelChunk(Level world, ChunkPos pos, ChunkBiomeContainer biomes, UpgradeData upgradeData, TickList<Block> blockTickScheduler, TickList<Fluid> fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable Consumer<LevelChunk> loadToWorldConsumer) {
- this.sections = new LevelChunkSection[16];
- this.pendingBlockEntities = Maps.newHashMap();
- this.heightmaps = Maps.newEnumMap(Heightmap.Types.class);
-- this.blockEntities = Maps.newHashMap();
-+ this.blockEntities = new TileEntityHashMap(); // Paper
- this.structureStarts = Maps.newHashMap();
- this.structuresRefences = Maps.newHashMap();
- this.postProcessing = new ShortList[16];
-@@ -504,6 +529,7 @@ public class LevelChunk implements ChunkAccess {
- }
-
- entity.inChunk = true;
-+ entity.setCurrentChunk(this); // Paper
- entity.xChunk = this.chunkPos.x;
- entity.yChunk = k;
- entity.zChunk = this.chunkPos.z;
-@@ -516,6 +542,7 @@ public class LevelChunk implements ChunkAccess {
- ((Heightmap) this.heightmaps.get(type)).setRawData(heightmap);
- }
-
-+ public final void removeEntity(Entity entity) { this.removeEntity(entity); } // Paper - OBFHELPER
- public void removeEntity(Entity entity) {
- this.removeEntity(entity, entity.yChunk);
- }
-@@ -530,7 +557,12 @@ public class LevelChunk implements ChunkAccess {
- section = this.entitySlices.length - 1;
- }
-
-- this.entitySlices[section].remove(entity);
-+ // Paper start
-+ if (entity.currentChunk != null && entity.currentChunk.get() == this) entity.setCurrentChunk(null);
-+ if (!this.entitySlices[section].remove(entity)) {
-+ return;
-+ }
-+ // Paper end
- this.entities.remove(entity); // Paper
- }
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-index 080d3292e03c5a179b9eb89da1550718d263f817..eb61c803cf74c5ca2c51d5027a02ed3db6b53096 100644
---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java
-@@ -145,6 +145,7 @@ import net.minecraft.world.entity.vehicle.MinecartHopper;
- import net.minecraft.world.entity.vehicle.MinecartSpawner;
- import net.minecraft.world.entity.vehicle.MinecartTNT;
- import net.minecraft.world.phys.AABB;
-+import org.bukkit.Chunk; // Paper
- import org.bukkit.EntityEffect;
- import org.bukkit.Location;
- import org.bukkit.Server;
-@@ -186,6 +187,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity {
- this.entity = entity;
- }
-
-+ @Override
-+ public Chunk getChunk() {
-+ net.minecraft.world.level.chunk.LevelChunk currentChunk = entity.getCurrentChunk();
-+ return currentChunk != null ? currentChunk.bukkitChunk : getLocation().getChunk();
-+ }
-+
- public static CraftEntity getEntity(CraftServer server, Entity entity) {
- /*
- * Order is *EXTREMELY* important -- keep it right! =D
diff --git a/patches/removed/1.17/0010-Store-counts-for-each-Entity-Block-Entity-Type.patch b/patches/removed/1.17/0010-Store-counts-for-each-Entity-Block-Entity-Type.patch
deleted file mode 100644
index ca1271ce76..0000000000
--- a/patches/removed/1.17/0010-Store-counts-for-each-Entity-Block-Entity-Type.patch
+++ /dev/null
@@ -1,55 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 4 Jul 2018 02:13:59 -0400
-Subject: [PATCH] Store counts for each Entity/Block Entity Type
-
-Opens door for future patches to optimize performance
-
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index ae08fcce66d50d7f61bc3bd4a0e2547d56f53e82..00ce55c17980da87a3834f952475a766543506b0 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -90,15 +90,19 @@ public class LevelChunk implements ChunkAccess {
- }
-
- // Paper start
-+ public final co.aikar.util.Counter<String> entityCounts = new co.aikar.util.Counter<>();
-+ public final co.aikar.util.Counter<String> tileEntityCounts = new co.aikar.util.Counter<>();
- private class TileEntityHashMap extends java.util.HashMap<BlockPos, BlockEntity> {
- @Override
- public BlockEntity put(BlockPos key, BlockEntity value) {
- BlockEntity replaced = super.put(key, value);
- if (replaced != null) {
- replaced.setCurrentChunk(null);
-+ tileEntityCounts.decrement(replaced.getMinecraftKeyString());
- }
- if (value != null) {
- value.setCurrentChunk(LevelChunk.this);
-+ tileEntityCounts.increment(value.getMinecraftKeyString());
- }
- return replaced;
- }
-@@ -108,6 +112,7 @@ public class LevelChunk implements ChunkAccess {
- BlockEntity removed = super.remove(key);
- if (removed != null) {
- removed.setCurrentChunk(null);
-+ tileEntityCounts.decrement(removed.getMinecraftKeyString());
- }
- return removed;
- }
-@@ -528,6 +533,7 @@ public class LevelChunk implements ChunkAccess {
- k = this.entitySlices.length - 1;
- }
-
-+ if (!entity.inChunk || entity.getCurrentChunk() != this) entityCounts.increment(entity.getMinecraftKeyString()); // Paper
- entity.inChunk = true;
- entity.setCurrentChunk(this); // Paper
- entity.xChunk = this.chunkPos.x;
-@@ -562,6 +568,7 @@ public class LevelChunk implements ChunkAccess {
- if (!this.entitySlices[section].remove(entity)) {
- return;
- }
-+ entityCounts.decrement(entity.getMinecraftKeyString());
- // Paper end
- this.entities.remove(entity); // Paper
- }
diff --git a/patches/removed/1.17/0351-Fix-issues-with-entity-loss-due-to-unloaded-chunks.patch b/patches/removed/1.17/0351-Fix-issues-with-entity-loss-due-to-unloaded-chunks.patch
deleted file mode 100644
index ae3a548ddb..0000000000
--- a/patches/removed/1.17/0351-Fix-issues-with-entity-loss-due-to-unloaded-chunks.patch
+++ /dev/null
@@ -1,53 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Fri, 28 Sep 2018 21:49:53 -0400
-Subject: [PATCH] Fix issues with entity loss due to unloaded chunks
-
-Vanilla has risk of losing entities by causing them to be
-removed from all chunks if they try to move into an unloaded chunk.
-
-This pretty much means high chance this entity will be lost in this
-scenario.
-
-There is another case that adding an entity to the world can fail if
-the chunk isn't loaded.
-
-Lots of the server is designed around addEntity never expecting to fail
-for these reasons, nor is it really logical.
-
-This change ensures the chunks are always loaded when entities are
-added to the world, or a valid entity moves between chunks.
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index d03b4f97102dfb88927a94ee5a5d397ac493eaa1..99883c83c126405fc93becefed8a1d0727b94aa7 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -848,11 +848,18 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- int k = Mth.floor(entity.getZ() / 16.0D);
-
- if (!entity.inChunk || entity.xChunk != i || entity.yChunk != j || entity.zChunk != k) {
-+ // Paper start - remove entity if its in a chunk more correctly.
-+ LevelChunk currentChunk = entity.getCurrentChunk();
-+ if (currentChunk != null) {
-+ currentChunk.removeEntity(entity);
-+ }
-+ // Paper end
-+
- if (entity.inChunk && this.hasChunk(entity.xChunk, entity.zChunk)) {
- this.getChunk(entity.xChunk, entity.zChunk).removeEntity(entity, entity.yChunk);
- }
-
-- if (!entity.checkAndResetForcedChunkAdditionFlag() && !this.hasChunk(i, k)) {
-+ if (!entity.valid && !entity.checkAndResetForcedChunkAdditionFlag() && !this.hasChunk(i, k)) { // Paper - always load chunks to register valid entities location
- if (entity.inChunk) {
- ServerLevel.LOGGER.warn("Entity {} left loaded chunk area", entity);
- }
-@@ -1067,7 +1074,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- return false;
- }
- // CraftBukkit end
-- ChunkAccess ichunkaccess = this.getChunk(Mth.floor(entity.getX() / 16.0D), Mth.floor(entity.getZ() / 16.0D), ChunkStatus.FULL, entity.forcedLoading);
-+ ChunkAccess ichunkaccess = this.getChunk(Mth.floor(entity.getX() / 16.0D), Mth.floor(entity.getZ() / 16.0D), ChunkStatus.FULL, true); // Paper - always load chunks for entity adds
-
- if (!(ichunkaccess instanceof LevelChunk)) {
- return false;
diff --git a/patches/removed/1.17/0372-Reduce-sync-loads.patch b/patches/removed/1.17/0372-Reduce-sync-loads.patch
deleted file mode 100644
index f2ae73064c..0000000000
--- a/patches/removed/1.17/0372-Reduce-sync-loads.patch
+++ /dev/null
@@ -1,33 +0,0 @@
-These hunks are for getEntities, which Mojang rewrote in 1.17.
-
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 3a1b9f1ba19b28cebdafeb3b2476217d47213862..3e2cd6c7a34c1a792d7346019a8b039d1f4a7c04 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -1130,7 +1130,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
-
- for (int i1 = i; i1 <= j; ++i1) {
- for (int j1 = k; j1 <= l; ++j1) {
-- LevelChunk chunk = ichunkprovider.getChunk(i1, j1, false);
-+ LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
-
- if (chunk != null) {
- chunk.getEntities(except, box, list, predicate);
-@@ -1151,7 +1151,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
-
- for (int i1 = i; i1 < j; ++i1) {
- for (int j1 = k; j1 < l; ++j1) {
-- LevelChunk chunk = this.getChunkSource().getChunk(i1, j1, false);
-+ LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
-
- if (chunk != null) {
- chunk.getEntities(type, box, list, predicate);
-@@ -1174,7 +1174,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
-
- for (int i1 = i; i1 < j; ++i1) {
- for (int j1 = k; j1 < l; ++j1) {
-- LevelChunk chunk = ichunkprovider.getChunk(i1, j1, false);
-+ LevelChunk chunk = (LevelChunk)this.getChunkIfLoadedImmediately(i1, j1); // Paper
-
- if (chunk != null) {
- chunk.getEntitiesOfClass(entityClass, box, list, predicate);
diff --git a/patches/removed/1.17/0380-Optimise-random-block-ticking.patch b/patches/removed/1.17/0380-Optimise-random-block-ticking.patch
deleted file mode 100644
index 497cf2c771..0000000000
--- a/patches/removed/1.17/0380-Optimise-random-block-ticking.patch
+++ /dev/null
@@ -1,397 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 27 Jan 2020 21:28:00 -0800
-Subject: [PATCH] Optimise random block ticking
-
-Massive performance improvement for random block ticking.
-The performance increase comes from the fact that the vast
-majority of attempted block ticks (~95% in my testing) fail
-because the randomly selected block is not tickable.
-
-Now only tickable blocks are targeted, however this means that
-the maximum number of block ticks occurs per chunk. However,
-not all chunks are going to be targeted. The percent chance
-of a chunk being targeted is based on how many tickable blocks
-are in the chunk.
-This means that while block ticks are spread out less, the
-total number of blocks ticked per world tick remains the same.
-Therefore, the chance of a random tickable block being ticked
-remains the same.
-
-1.17: The IBlockDataList util class needs to be redone to support variable height limits
-
-diff --git a/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java b/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..3edc8e52e06a62ce9f8cc734fd7458b37cfaad91
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/util/math/ThreadUnsafeRandom.java
-@@ -0,0 +1,46 @@
-+package com.destroystokyo.paper.util.math;
-+
-+import java.util.Random;
-+
-+public final class ThreadUnsafeRandom extends Random {
-+
-+ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them.
-+ private static final long multiplier = 0x5DEECE66DL;
-+ private static final long addend = 0xBL;
-+ private static final long mask = (1L << 48) - 1;
-+
-+ private static long initialScramble(long seed) {
-+ return (seed ^ multiplier) & mask;
-+ }
-+
-+ private long seed;
-+
-+ @Override
-+ public void setSeed(long seed) {
-+ // note: called by Random constructor
-+ this.seed = initialScramble(seed);
-+ }
-+
-+ @Override
-+ protected int next(int bits) {
-+ // avoid the expensive CAS logic used by superclass
-+ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits));
-+ }
-+
-+ // Taken from
-+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
-+ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c
-+ // Original license is public domain
-+ public static int fastRandomBounded(final long randomInteger, final long limit) {
-+ // randomInteger must be [0, pow(2, 32))
-+ // limit must be [0, pow(2, 32))
-+ return (int)((randomInteger * limit) >>> 32);
-+ }
-+
-+ @Override
-+ public int nextInt(int bound) {
-+ // yes this breaks random's spec
-+ // however there's nothing that uses this class that relies on it
-+ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound);
-+ }
-+}
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 3b7585760483b077783a28de8d04ba438eb25c16..5f499f2d8e62fc6f28c180c857582bd6c895c98c 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -673,7 +673,12 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- });
- }
-
-- public void tickChunk(LevelChunk chunk, int randomTickSpeed) {
-+ // Paper start - optimise random block ticking
-+ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos();
-+ private final com.destroystokyo.paper.util.math.ThreadUnsafeRandom randomTickRandom = new com.destroystokyo.paper.util.math.ThreadUnsafeRandom();
-+ // Paper end
-+
-+ public void tickChunk(LevelChunk chunk, int randomTickSpeed) { final int randomTickSpeed1 = randomTickSpeed; // Paper
- ChunkPos chunkcoordintpair = chunk.getPos();
- boolean flag = this.isRaining();
- int j = chunkcoordintpair.getMinBlockX();
-@@ -681,10 +686,10 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- ProfilerFiller gameprofilerfiller = this.getProfiler();
-
- gameprofilerfiller.push("thunder");
-- BlockPos blockposition;
-+ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change
-
- if (!this.paperConfig.disableThunder && flag && this.isThundering() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder
-- blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15));
-+ blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper
- if (this.isRainingAt(blockposition)) {
- DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition);
- boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * paperConfig.skeleHorseSpawnChance && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper
-@@ -707,66 +712,81 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- }
-
- gameprofilerfiller.popPush("iceandsnow");
-- if (!this.paperConfig.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow
-- blockposition = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.getBlockRandomPos(j, 0, k, 15));
-- BlockPos blockposition1 = blockposition.below();
-+ if (!this.paperConfig.disableIceAndSnow && this.randomTickRandom.nextInt(16) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking
-+ // Paper start - optimise chunk ticking
-+ this.getRandomBlockPosition(j, 0, k, 15, blockposition);
-+ int normalY = chunk.getHighestBlockY(Heightmap.Types.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15);
-+ int downY = normalY - 1;
-+ blockposition.setY(normalY);
-+ // Paper end
- Biome biomebase = this.getBiome(blockposition);
-
-- if (biomebase.shouldFreeze((LevelReader) this, blockposition1)) {
-- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
-+ // Paper start - optimise chunk ticking
-+ blockposition.setY(downY);
-+ if (biomebase.shouldFreeze(this, blockposition)) {
-+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.ICE.defaultBlockState(), null); // CraftBukkit
-+ // Paper end
- }
-
- if (flag) {
-+ blockposition.setY(normalY); // Paper
- if (biomebase.shouldSnow(this, blockposition)) {
- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit
- }
-
-- BlockState iblockdata = this.getBlockState(blockposition1);
-+ blockposition.setY(downY); // Paper
-+ BlockState iblockdata = this.getBlockState(blockposition); // Paper
- Biome.Precipitation biomebase_precipitation = this.getBiome(blockposition).getPrecipitation();
-
-- if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.isColdEnoughToSnow(blockposition1)) {
-+ if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.isColdEnoughToSnow(blockposition)) { // Paper
- biomebase_precipitation = Biome.Precipitation.SNOW;
- }
-
-- iblockdata.getBlock().handlePrecipitation(iblockdata, (net.minecraft.world.level.Level) this, blockposition1, biomebase_precipitation);
-+ iblockdata.getBlock().handlePrecipitation(iblockdata, (net.minecraft.world.level.Level) this, blockposition, biomebase_precipitation); // Paper
- }
- }
-
-- gameprofilerfiller.popPush("tickBlocks");
-- timings.chunkTicksBlocks.startTiming(); // Paper
-+ // Paper start - optimise random block ticking
-+ gameprofilerfiller.pop();
- if (randomTickSpeed > 0) {
-- LevelChunkSection[] achunksection = chunk.getSections();
-- int l = achunksection.length;
--
-- for (int i1 = 0; i1 < l; ++i1) {
-- LevelChunkSection chunksection = achunksection[i1];
-+ gameprofilerfiller.push("randomTick");
-+ timings.chunkTicksBlocks.startTiming(); // Paper
-
-- if (chunksection != LevelChunk.EMPTY_SECTION && chunksection.isRandomlyTicking()) {
-- int j1 = chunksection.bottomBlockY();
-+ LevelChunkSection[] sections = chunk.getSections();
-
-- for (int k1 = 0; k1 < randomTickSpeed; ++k1) {
-- BlockPos blockposition2 = this.getBlockRandomPos(j, j1, k, 15);
-+ for (int sectionIndex = 0; sectionIndex < 16; ++sectionIndex) {
-+ LevelChunkSection section = sections[sectionIndex];
-+ if (section == null || section.tickingList.size() == 0) {
-+ continue;
-+ }
-
-- gameprofilerfiller.push("randomTick");
-- BlockState iblockdata1 = chunksection.getBlockState(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k);
-+ int yPos = sectionIndex << 4;
-+ for (int a = 0; a < randomTickSpeed1; ++a) {
-+ int tickingBlocks = section.tickingList.size();
-+ int index = this.randomTickRandom.nextInt(16 * 16 * 16);
-+ if (index >= tickingBlocks) {
-+ continue;
-+ }
-
-- if (iblockdata1.isRandomlyTicking()) {
-- iblockdata1.randomTick(this, blockposition2, this.random);
-- }
-+ long raw = section.tickingList.getRaw(index);
-+ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw);
-+ int randomX = location & 15;
-+ int randomY = ((location >>> (4 + 4)) & 255) | yPos;
-+ int randomZ = (location >>> 4) & 15;
-
-- FluidState fluid = iblockdata1.getFluidState();
-+ BlockPos blockposition2 = blockposition.setValues(j + randomX, randomY, k + randomZ);
-+ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw);
-
-- if (fluid.isRandomlyTicking()) {
-- fluid.randomTick(this, blockposition2, this.random);
-- }
-+ iblockdata.randomTick(this, blockposition2, this.randomTickRandom);
-
-- gameprofilerfiller.pop();
-- }
-+ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method.
-+ // TODO CHECK ON UPDATE
- }
- }
-+ gameprofilerfiller.pop();
-+ timings.chunkTicksBlocks.stopTiming(); // Paper
-+ // Paper end
- }
-- timings.chunkTicksBlocks.stopTiming(); // Paper
-- gameprofilerfiller.pop();
- }
-
- private Optional<BlockPos> findLightningRod(BlockPos pos) {
-diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java
-index 9b955a027bd2c3cbcfa659a41a6687221c5fea63..6c036335b28258cd8c268173d73707af00d12bf9 100644
---- a/src/main/java/net/minecraft/util/BitStorage.java
-+++ b/src/main/java/net/minecraft/util/BitStorage.java
-@@ -105,4 +105,32 @@ public class BitStorage {
- }
-
- }
-+
-+ // Paper start
-+ public final void forEach(DataBitConsumer consumer) {
-+ int i = 0;
-+ long[] along = this.data;
-+ int j = along.length;
-+
-+ for (int k = 0; k < j; ++k) {
-+ long l = along[k];
-+
-+ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) {
-+ consumer.accept(i, (int) (l & this.mask));
-+ l >>= this.bits;
-+ ++i;
-+ if (i >= this.size) {
-+ return;
-+ }
-+ }
-+ }
-+ }
-+
-+ @FunctionalInterface
-+ public static interface DataBitConsumer {
-+
-+ void accept(int location, int data);
-+
-+ }
-+ // Paper end
- }
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
-index 925f16d5eb092518ef774f69a8d99689feb0f5d7..01d8af06f19427354cac95d691e65d31253fef94 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java
-@@ -91,7 +91,7 @@ public class Turtle extends Animal {
- }
-
- public void setHomePos(BlockPos pos) {
-- this.entityData.set(Turtle.HOME_POS, pos);
-+ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos...
- }
-
- public BlockPos getHomePos() { // Paper - public
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 02c4396f3f42c1ec387eae9b2f7815f6e9f9e1c4..1e373db7080bd4fa5c62188e3ddb3e5206e9b5b1 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -1307,10 +1307,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- public abstract TagContainer getTagManager();
-
- public BlockPos getBlockRandomPos(int x, int y, int z, int l) {
-+ // Paper start - allow use of mutable pos
-+ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos();
-+ this.getRandomBlockPosition(x, y, z, l, ret);
-+ return ret.immutable();
-+ }
-+ public final BlockPos.MutableBlockPos getRandomBlockPosition(int i, int j, int k, int l, BlockPos.MutableBlockPos out) {
-+ // Paper end
- this.randValue = this.randValue * 3 + 1013904223;
- int i1 = this.randValue >> 2;
-
-- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15));
-+ out.setValues(i + (i1 & 15), j + (i1 >> 16 & l), k + (i1 >> 8 & 15)); // Paper - change to setValues call
-+ return out; // Paper
- }
-
- public boolean noSave() {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index a088cb005525fda2c9d5521ab3bac43cfa38a393..1782be43f1dbe2776abe5087d305e271c62285dd 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -568,6 +568,7 @@ public class LevelChunk implements ChunkAccess {
- @Override
- public void addEntity(Entity entity) {}
-
-+ public final int getHighestBlockY(Heightmap.Types heightmap_type, int i, int j) { return this.getHeight(heightmap_type, i, j) + 1; } // Paper - sort of an obfhelper, but without -1
- @Override
- public int getHeight(Heightmap.Types type, int x, int z) {
- return ((Heightmap) this.heightmaps.get(type)).getFirstAvailable(x & 15, z & 15) - 1;
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-index ec8b67c1b024df38d5e1ad81acff33537ae25626..739abc73020e8a41a99fa52907843efe07af9b4e 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
-@@ -14,11 +14,12 @@ public class LevelChunkSection {
- public static final int SECTION_HEIGHT = 16;
- public static final int SECTION_SIZE = 4096;
- public static final Palette<BlockState> GLOBAL_BLOCKSTATE_PALETTE = new GlobalPalette<>(Block.BLOCK_STATE_REGISTRY, Blocks.AIR.defaultBlockState());
-- private final int bottomBlockY;
-+ final int bottomBlockY; // Paper - private -> package-private
- short nonEmptyBlockCount; // Paper - package-private
-- private short tickingBlockCount;
-+ short tickingBlockCount; // Paper - private -> package-private
- private short tickingFluidCount;
- final PalettedContainer<BlockState> states; // Paper - package-private
-+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper
-
- // Paper start - Anti-Xray - Add parameters
- @Deprecated public LevelChunkSection(int yOffset) { this(yOffset, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere
-@@ -79,6 +80,9 @@ public class LevelChunkSection {
- --this.nonEmptyBlockCount;
- if (blockState.isRandomlyTicking()) {
- --this.tickingBlockCount;
-+ // Paper start
-+ this.tickingList.remove(x, y, z);
-+ // Paper end
- }
- }
-
-@@ -90,6 +94,9 @@ public class LevelChunkSection {
- ++this.nonEmptyBlockCount;
- if (state.isRandomlyTicking()) {
- ++this.tickingBlockCount;
-+ // Paper start
-+ this.tickingList.add(x, y, z, state);
-+ // Paper end
- }
- }
-
-@@ -125,22 +132,28 @@ public class LevelChunkSection {
- }
-
- public void recalcBlockCounts() {
-+ // Paper start
-+ this.tickingList.clear();
-+ // Paper end
- this.nonEmptyBlockCount = 0;
- this.tickingBlockCount = 0;
- this.tickingFluidCount = 0;
-- this.states.count((state, count) -> {
-+ this.states.forEachLocation((state, location) -> { // Paper
- FluidState fluidState = state.getFluidState();
- if (!state.isAir()) {
-- this.nonEmptyBlockCount = (short)(this.nonEmptyBlockCount + count);
-+ this.nonEmptyBlockCount = (short)(this.nonEmptyBlockCount + 1); // Paper
- if (state.isRandomlyTicking()) {
-- this.tickingBlockCount = (short)(this.tickingBlockCount + count);
-+ // Paper start
-+ this.tickingBlockCount = (short)(this.tickingBlockCount + 1);
-+ this.tickingList.add(location, state);
-+ // Paper end
- }
- }
-
- if (!fluidState.isEmpty()) {
-- this.nonEmptyBlockCount = (short)(this.nonEmptyBlockCount + count);
-+ this.nonEmptyBlockCount = (short) (this.nonEmptyBlockCount + 1); // Paper
- if (fluidState.isRandomlyTicking()) {
-- this.tickingFluidCount = (short)(this.tickingFluidCount + count);
-+ this.tickingFluidCount = (short) (this.tickingFluidCount + 1); // Paper
- }
- }
-
-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 b5b0dbbb21f15a61017d8fc936feed30c2b193dc..2bedc0f38ef16e922197a6f8e4c17aeb9cab92fd 100644
---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-@@ -314,6 +314,14 @@ public class PalettedContainer<T> implements PaletteResize<T> {
- });
- }
-
-+ // Paper start
-+ public void forEachLocation(PalettedContainer.CountConsumer<T> datapaletteblock_a) {
-+ this.getDataBits().forEach((int location, int data) -> {
-+ datapaletteblock_a.accept(this.getDataPalette().getObject(data), location);
-+ });
-+ }
-+ // Paper end
-+
- @FunctionalInterface
- public interface CountConsumer<T> {
- void accept(T object, int count);
diff --git a/patches/removed/1.17/0420-Ensure-Entity-is-never-double-registered.patch b/patches/removed/1.17/0420-Ensure-Entity-is-never-double-registered.patch
deleted file mode 100644
index a10ff17d78..0000000000
--- a/patches/removed/1.17/0420-Ensure-Entity-is-never-double-registered.patch
+++ /dev/null
@@ -1,81 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sun, 29 Mar 2020 18:26:14 -0400
-Subject: [PATCH] Ensure Entity is never double registered
-
-If something calls register twice, and the world is ticking, it could be
-enqueued to add twice.
-
-Vs behavior of non ticking of just overwriting state.
-
-We will now simply log a warning when this happens instead of crashing the server.
-
-1.17: Probably not needed?
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 9da0d98bc2ed7876a00a734690ed42f01b9a9a9b..9898d5c8fab63c576831bd416ccf1854ed077b0d 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -643,6 +643,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- Entity entity2;
-
- while ((entity2 = (Entity) this.toAddAfterTick.poll()) != null) {
-+ if (!entity2.isQueuedForRegister) continue; // Paper - ignore cancelled registers
- this.add(entity2);
- }
-
-@@ -1400,6 +1401,19 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
-
- public void onEntityRemoved(Entity entity) {
- org.spigotmc.AsyncCatcher.catchOp("entity unregister"); // Spigot
-+ // Paper start - fix entity registration issues
-+ if (entity instanceof EnderDragonPart) {
-+ // Usually this is a no-op for complex parts, and ID's should be removed, but go ahead and remove it anyways
-+ // Dragon parts are handled special in register. they don't receive a valid = true or register by UUID etc.
-+ this.entitiesById.remove(entity.getId(), entity);
-+ return;
-+ }
-+ if (!entity.valid) {
-+ // Someone called remove before we ever got added, cancel the add.
-+ entity.isQueuedForRegister = false;
-+ return;
-+ }
-+ // Paper end
- // Spigot start
- if ( entity instanceof Player )
- {
-@@ -1466,9 +1480,21 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
-
- private void add(Entity entity) {
- org.spigotmc.AsyncCatcher.catchOp("entity register"); // Spigot
-+ // Paper start - don't double enqueue entity registration
-+ //noinspection ObjectEquality
-+ if (this.entitiesById.get(entity.getId()) == entity) {
-+ LOGGER.error(entity + " was already registered!");
-+ new Throwable().printStackTrace();
-+ return;
-+ }
-+ // Paper end
- if (this.tickingEntities) {
-- this.toAddAfterTick.add(entity);
-+ if (!entity.isQueuedForRegister) { // Paper
-+ this.toAddAfterTick.add(entity);
-+ entity.isQueuedForRegister = true; // Paper
-+ }
- } else {
-+ entity.isQueuedForRegister = false; // Paper
- this.entitiesById.put(entity.getId(), entity);
- if (entity instanceof EnderDragon) {
- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity).getSubEntities();
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 43f77d01fceab107d3502d282205aa579d64cc4b..7e198b94f349d4c4d61502f5ad8c60686800d88f 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -147,6 +147,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
- }
-
- // Paper start
-+ public boolean isQueuedForRegister = false;
- public static Random SHARED_RANDOM = new Random() {
- private boolean locked = false;
- @Override
diff --git a/patches/removed/1.17/0436-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch b/patches/removed/1.17/0436-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch
deleted file mode 100644
index 4dfb0ddf1d..0000000000
--- a/patches/removed/1.17/0436-Delay-unsafe-actions-until-after-entity-ticking-is-d.patch
+++ /dev/null
@@ -1,47 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 11 Apr 2020 21:23:42 -0400
-Subject: [PATCH] Delay unsafe actions until after entity ticking is done
-
-This will help prevent many cases of unregistering entities during entity ticking
-
-1.17: Not used anywhere in 1.16.5 server, and no more tickingEntities bool on ServerLevel (moved to Level?)
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 4e75cc5e52a5295e32ccadb371702a405bb518bb..b9978d296b83e73d3395b8254c0e8ccd9b36d0fa 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -172,6 +172,16 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- public final List<ServerPlayer> players = Lists.newArrayList(); // Paper - private -> public
- public final ServerChunkCache chunkSource; // Paper - public
- boolean tickingEntities;
-+ // Paper start
-+ List<java.lang.Runnable> afterEntityTickingTasks = Lists.newArrayList();
-+ public void doIfNotEntityTicking(java.lang.Runnable run) {
-+ if (tickingEntities) {
-+ afterEntityTickingTasks.add(run);
-+ } else {
-+ run.run();
-+ }
-+ }
-+ // Paper end
- private final MinecraftServer server;
- public final PrimaryLevelData worldDataServer; // CraftBukkit - type
- public boolean noSave;
-@@ -641,6 +651,16 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- timings.entityTick.stopTiming(); // Spigot
-
- this.tickingEntities = false;
-+ // Paper start
-+ for (java.lang.Runnable run : this.afterEntityTickingTasks) {
-+ try {
-+ run.run();
-+ } catch (Exception e) {
-+ LOGGER.error("Error in After Entity Ticking Task", e);
-+ }
-+ }
-+ this.afterEntityTickingTasks.clear();
-+ // Paper end
- this.getServer().midTickLoadChunks(); // Paper
-
- Entity entity2;
diff --git a/patches/removed/1.17/0438-Workaround-for-Client-Lag-Spikes-MC-162253.patch b/patches/removed/1.17/0438-Workaround-for-Client-Lag-Spikes-MC-162253.patch
deleted file mode 100644
index f5780c7a9a..0000000000
--- a/patches/removed/1.17/0438-Workaround-for-Client-Lag-Spikes-MC-162253.patch
+++ /dev/null
@@ -1,101 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: MeFisto94 <[email protected]>
-Date: Tue, 12 May 2020 23:02:43 +0200
-Subject: [PATCH] Workaround for Client Lag Spikes (MC-162253)
-
-When crossing certain chunk boundaries, the client needlessly
-calculates light maps for chunk neighbours. In some specific map
-configurations, these calculations cause a 500ms+ freeze on the Client.
-
-This patch basically serves as a workaround by sending light maps
-to the client, so that it doesn't attempt to calculate them.
-This mitigates the frametime impact to a minimum (but it's still there).
-
-1.17 UPDATE NOTE: More or less untested, mapped version of the patch: https://paste.gg/p/anonymous/594123c7ce7d4d398d8834af6ba386d1
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index d8f99f7f5ca0e1dbbb9b760af3a4b4f9c52ef6c7..f700ac973ebc3037a5a44eac3c9d505b98adce41 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -1906,9 +1906,68 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
-
- public void playerLoadedChunk(ServerPlayer player, Packet<?>[] packets, LevelChunk chunk) { // Paper - private -> public
- if (packets[0] == null) {
-+ // Paper start - add 8 for light fix workaround
-+ if (packets.length != 10) { // in case Plugins call sendChunk, resize
-+ packets = new Packet[10];
-+ }
-+ // Paper end
- packets[0] = new ClientboundLevelChunkPacket(chunk);
- packets[1] = new ClientboundLightUpdatePacket(chunk.getPos(), this.lightEngine, (BitSet) null, (BitSet) null, true);
-+
-+ // Paper start - Fix MC-162253
-+ final int lightMask = getLightMask(chunk);
-+ int i = 1;
-+ for (int x = -1; x <= 1; x++) {
-+ for (int z = -1; z <= 1; z++) {
-+ if (x == 0 && z == 0) {
-+ continue;
-+ }
-+
-+ ++i;
-+
-+ if (!chunk.isNeighbourLoaded(x, z)) {
-+ continue;
-+ }
-+
-+ final LevelChunk neighbor = chunk.getRelativeNeighbourIfLoaded(x, z);
-+ final int updateLightMask = lightMask & ~getCeilingLightMask(neighbor);
-+
-+ if (updateLightMask == 0) {
-+ continue;
-+ }
-+
-+ packets[i] = new ClientboundLightUpdatePacket(new ChunkPos(chunk.getPos().x + x, chunk.getPos().z + z), lightEngine, null, null, updateLightMask, 0, true); // TODO: This line needs updating
-+ }
-+ }
-+ }
-+
-+ final int viewDistance = playerViewDistanceBroadcastMap.getLastViewDistance(player);
-+ final long lastPosition = playerViewDistanceBroadcastMap.getLastCoordinate(player);
-+
-+ int j = 1;
-+ for (int x = -1; x <= 1; x++) {
-+ for (int z = -1; z <= 1; z++) {
-+ if (x == 0 && z == 0) {
-+ continue;
-+ }
-+
-+ ++j;
-+
-+ Packet<?> packet = packets[j];
-+ if (packet == null) {
-+ continue;
-+ }
-+
-+ final int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - (chunk.getPos().x + x));
-+ final int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - (chunk.getPos().z + z));
-+
-+ if (Math.max(distX, distZ) > viewDistance) {
-+ continue;
-+ }
-+ player.connection.send(packet);
-+ }
- }
-+ // Paper end - Fix MC-162253
-
- player.trackChunk(chunk.getPos(), packets[0], packets[1]);
- DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos());
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index a63dc77db41dab79f03ef7384da55c1cdeca5d98..7cced5d06f296fcdc1209a43e7b3d1d9b47c0b26 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -262,7 +262,7 @@ public class LevelChunk implements ChunkAccess {
-
- // broadcast
- Object[] backingSet = inRange.getBackingSet();
-- Packet[] chunkPackets = new Packet[2];
-+ Packet[] chunkPackets = new Packet[10];
- for (int index = 0, len = backingSet.length; index < len; ++index) {
- Object temp = backingSet[index];
- if (!(temp instanceof net.minecraft.server.level.ServerPlayer)) {
diff --git a/patches/removed/1.17/0626-Optimized-tick-ready-check.patch b/patches/removed/1.17/0626-Optimized-tick-ready-check.patch
deleted file mode 100644
index a054be2fb3..0000000000
--- a/patches/removed/1.17/0626-Optimized-tick-ready-check.patch
+++ /dev/null
@@ -1,52 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: lukas <[email protected]>
-Date: Sun, 27 Dec 2020 17:19:51 +0100
-Subject: [PATCH] Optimized tick ready check
-
-1.17: Needs to be reworked or dropped
-
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 066d5f7ee93351bff67c0d39ee9d940ac51515d8..b89cefc8890774dbc64fd6bddeb038d2ee36d485 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -854,13 +854,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- if (!tileentity.isRemoved() && tileentity.hasLevel()) {
- BlockPos blockposition = tileentity.getBlockPos();
-
-- if (this.getChunkSource().isTickingChunk(blockposition) && this.getWorldBorder().isWithinBounds(blockposition)) {
-+ LevelChunk chunk; ChunkHolder playerChunk; if ((chunk = tileentity.getCurrentChunk()) != null && (playerChunk = chunk.playerChunk) != null && playerChunk.isTickingReady() && this.getWorldBorder().isInBounds(blockposition)) { // Paper - optimized tick ready check by inlining ChunkProviderServer.a(BlockPosition). Chunk lookup is no longer required and we can use the PlayerChunk directly available through the tile entity
- try {
- gameprofilerfiller.push(() -> {
- return String.valueOf(BlockEntityType.getKey(tileentity.getType()));
- });
- tileentity.tickTimer.startTiming(); // Spigot
-- if (tileentity.getType().isValid(this.getBlockState(blockposition).getBlock())) {
-+ if (tileentity.getType().isValid(chunk.getBlockState(blockposition).getBlock())) { // Paper - reuse the chunk from above, do not look it up again
- ((TickableBlockEntity) tileentity).tick();
- } else {
- tileentity.logInvalidState();
-@@ -893,9 +893,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- this.tickableBlockEntities.remove(tileTickPosition--);
- // Spigot end
- //this.tileEntityList.remove(tileentity); // Paper - remove unused list
-- if (this.hasChunkAt(tileentity.getBlockPos())) {
-- this.getChunkAt(tileentity.getBlockPos()).removeBlockEntity(tileentity.getBlockPos());
-+ // Paper - prevent double chunk lookups
-+ LevelChunk chunk; if ((chunk = this.getChunkIfLoaded(tileentity.getBlockPos())) != null) { // inlined contents of this.isLoaded(BlockPosition). Reuse the returned chunk instead of looking it up again
-+ chunk.removeBlockEntity(tileentity.getBlockPos());
- }
-+ // Paper end
- }
- }
-
-@@ -914,8 +916,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- }
- // CraftBukkit end */
-
-- if (this.hasChunkAt(tileentity1.getBlockPos())) {
-- LevelChunk chunk = this.getChunkAt(tileentity1.getBlockPos());
-+ LevelChunk chunk; if ((chunk = this.getChunkIfLoaded(tileentity1.getBlockPos())) != null) { // Paper - inlined contents of this.isLoaded(BlockPosition). Reuse the returned chunk instead of looking it up again
-+ // Chunk chunk = this.getChunkAtWorldCoords(tileentity1.getPosition()); // Paper - already computed above
- BlockState iblockdata = chunk.getBlockState(tileentity1.getBlockPos());
-
- chunk.setBlockEntity(tileentity1.getBlockPos(), tileentity1);
diff --git a/patches/removed/1.17/0698-Make-sure-to-remove-correct-TE-during-TE-tick.patch b/patches/removed/1.17/0698-Make-sure-to-remove-correct-TE-during-TE-tick.patch
deleted file mode 100644
index 1998d6b9d6..0000000000
--- a/patches/removed/1.17/0698-Make-sure-to-remove-correct-TE-during-TE-tick.patch
+++ /dev/null
@@ -1,47 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 29 Mar 2021 09:07:25 +0200
-Subject: [PATCH] Make sure to remove correct TE during TE tick
-
-This looks like it can cause premature TE removal.
-
-1.17: doesnt apply anymore?
-
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index b89cefc8890774dbc64fd6bddeb038d2ee36d485..4523bc1f49e7be248a47eeb599fa7b6550dbb08d 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -895,7 +895,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- //this.tileEntityList.remove(tileentity); // Paper - remove unused list
- // Paper - prevent double chunk lookups
- LevelChunk chunk; if ((chunk = this.getChunkIfLoaded(tileentity.getBlockPos())) != null) { // inlined contents of this.isLoaded(BlockPosition). Reuse the returned chunk instead of looking it up again
-- chunk.removeBlockEntity(tileentity.getBlockPos());
-+ chunk.removeTileEntity(tileentity.getBlockPos(), tileentity); // Paper - remove correct TE
- }
- // Paper end
- }
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 9b76dc15417eef420804e5184a6d684e1137a746..a15c08be3e1bd0e7934175db6ae0684bbb05e249 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -818,10 +818,18 @@ public class LevelChunk implements ChunkAccess {
-
- @Override
- public void removeBlockEntity(BlockPos pos) {
-+ // Paper start - remove correct TE
-+ removeTileEntity(pos, null);
-+ }
-+ public void removeTileEntity(BlockPos blockposition, BlockEntity match) {
-+ // Paper end
- if (this.loaded || this.world.isClientSide()) {
-- BlockEntity tileentity = (BlockEntity) this.blockEntities.remove(pos);
-+ // Paper start
-+ BlockEntity tileentity = (BlockEntity) this.blockEntities.get(blockposition);
-
-- if (tileentity != null) {
-+ if (tileentity != null && (match == null || match == tileentity)) {
-+ this.blockEntities.remove(blockposition);
-+ // Paper end
- tileentity.setRemoved();
- }
- }
diff --git a/patches/removed/1.17/No longer needed/0025-Optimize-TileEntity-Ticking.patch b/patches/removed/1.17/No longer needed/0025-Optimize-TileEntity-Ticking.patch
deleted file mode 100644
index 79f438b892..0000000000
--- a/patches/removed/1.17/No longer needed/0025-Optimize-TileEntity-Ticking.patch
+++ /dev/null
@@ -1,281 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sun, 8 Mar 2015 22:55:25 -0600
-Subject: [PATCH] Optimize TileEntity Ticking
-
-ticking logic changes implemented by mojang
-
-
-diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
-index 94adf0275a2e7093c152cc3b8b0a5747b3a13a86..5bcf9cefc29eb20e2cfbfb49e2b2662ec394a87e 100644
---- a/src/main/java/co/aikar/timings/TimingsExport.java
-+++ b/src/main/java/co/aikar/timings/TimingsExport.java
-@@ -112,7 +112,7 @@ public class TimingsExport extends Thread {
- pair("end", System.currentTimeMillis() / 1000),
- pair("online-mode", Bukkit.getServer().getOnlineMode()),
- pair("sampletime", (System.currentTimeMillis() - TimingsManager.timingStart) / 1000),
-- pair("datapacks", toArrayMapper(MinecraftServer.getServer().getPackRepository().getSelectedIds(), pack -> {
-+ pair("datapacks", toArrayMapper(MinecraftServer.getServer().getPackRepository().getSelectedPacks(), pack -> {
- // Don't feel like obf helper'ing these, non fatal if its temp missed.
- return ChatColor.stripColor(CraftChatMessage.fromComponent(pack.a(true)));
- }))
-@@ -151,8 +151,8 @@ public class TimingsExport extends Thread {
- );
-
- parent.put("worlds", toObjectMapper(MinecraftServer.getServer().getAllLevels(), world -> {
-- if (world.getWorldData().getName().equals("worldeditregentempworld")) return null;
-- return pair(world.getWorldData().getName(), createObject(
-+ if (world.getWorld().getName().equals("worldeditregentempworld")) return null;
-+ return pair(world.getWorld().getName(), createObject(
- pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> {
- return pair(rule, world.getWorld().getGameRuleValue(rule));
- })),
-diff --git a/src/main/java/net/minecraft/world/level/block/ChestBlock.java b/src/main/java/net/minecraft/world/level/block/ChestBlock.java
-index 56656bf34db07bc717ace8ae9c1b60f9bfd7ff05..1bda9a158eb4372b9ab7cf3097732e64810aefc6 100644
---- a/src/main/java/net/minecraft/world/level/block/ChestBlock.java
-+++ b/src/main/java/net/minecraft/world/level/block/ChestBlock.java
-@@ -54,8 +54,8 @@ import net.minecraft.world.phys.shapes.VoxelShape;
- public class ChestBlock extends AbstractChestBlock<ChestBlockEntity> implements SimpleWaterloggedBlock {
-
- public static final DirectionProperty FACING = HorizontalDirectionalBlock.FACING;
-- public static final EnumProperty<ChestType> TYPE = BlockStateProperties.CHEST_TYPE;
-- public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED;
-+ public static final EnumProperty<ChestType> TYPE = BlockStateProperties.CHEST_TYPE; public static final EnumProperty<ChestType> CHEST_TYPE_PROPERTY = TYPE; // Paper - OBFHELPER
-+ public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; public static final BooleanProperty waterlogged() { return WATERLOGGED; } // Paper OBFHELPER
- protected static final VoxelShape NORTH_AABB = Block.box(1.0D, 0.0D, 0.0D, 15.0D, 14.0D, 15.0D);
- protected static final VoxelShape SOUTH_AABB = Block.box(1.0D, 0.0D, 1.0D, 15.0D, 14.0D, 16.0D);
- protected static final VoxelShape WEST_AABB = Block.box(0.0D, 0.0D, 1.0D, 15.0D, 14.0D, 15.0D);
-diff --git a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
-index 7b08ee35d2d8dc3fe783d773bf6686a5197006b8..17289d28b6d0023279a573715ee3d182988dd651 100644
---- a/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
-+++ b/src/main/java/net/minecraft/world/level/block/entity/ChestBlockEntity.java
-@@ -8,6 +8,7 @@ import net.minecraft.core.NonNullList;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.network.chat.Component;
- import net.minecraft.network.chat.TranslatableComponent;
-+import net.minecraft.server.MCUtil;
- import net.minecraft.sounds.SoundEvent;
- import net.minecraft.sounds.SoundEvents;
- import net.minecraft.sounds.SoundSource;
-@@ -32,7 +33,7 @@ import org.bukkit.craftbukkit.entity.CraftHumanEntity;
- import org.bukkit.entity.HumanEntity;
- // CraftBukkit end
-
--public class ChestBlockEntity extends RandomizableContainerBlockEntity implements TickableBlockEntity {
-+public class ChestBlockEntity extends RandomizableContainerBlockEntity { // Paper - Remove ITickable
-
- private NonNullList<ItemStack> items;
- protected float openness;
-@@ -110,14 +111,20 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
- return tag;
- }
-
-- @Override
- public void tick() {
- int i = this.worldPosition.getX();
- int j = this.worldPosition.getY();
- int k = this.worldPosition.getZ();
-
- ++this.tickInterval;
-- this.openCount = getOpenCount(this.level, this, this.tickInterval, i, j, k, this.openCount);
-+ }
-+
-+ public void doOpenLogic() {
-+ int i = this.worldPosition.getX();
-+ int j = this.worldPosition.getY();
-+ int k = this.worldPosition.getZ();
-+
-+ //this.viewingCount = a(this.world, this, this.j, i, j, k, this.viewingCount); // Paper - check is faulty given our logic is called before active container set
- this.oOpenness = this.openness;
- float f = 0.1F;
-
-@@ -131,25 +138,31 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
- if (this.openCount > 0 && this.openness == 0.0F) {
- this.playSound(SoundEvents.CHEST_OPEN);
- }
-+ }
-
-- if (this.openCount == 0 && this.openness > 0.0F || this.openCount > 0 && this.openness < 1.0F) {
-- float f1 = this.openness;
-+ public void doCloseLogic() {
-+ if (this.openCount == 0 /* && this.a > 0.0F || this.viewingCount > 0 && this.a < 1.0F */) { // Paper - disable all but player count check
-+ /* // Paper - disable animation stuff
-+ float f1 = this.a;
-
-- if (this.openCount > 0) {
-- this.openness += 0.1F;
-+ if (this.viewingCount > 0) {
-+ this.a += 0.1F;
- } else {
-- this.openness -= 0.1F;
-+ this.a -= 0.1F;
- }
-
-- if (this.openness > 1.0F) {
-- this.openness = 1.0F;
-+ if (this.a > 1.0F) {
-+ this.a = 1.0F;
- }
-
- float f2 = 0.5F;
-
-- if (this.openness < 0.5F && f1 >= 0.5F) {
-+ if (this.a < 0.5F && f1 >= 0.5F) {
-+ */
-+ MCUtil.scheduleTask(10, () -> {
- this.playSound(SoundEvents.CHEST_CLOSE);
-- }
-+ }, "Chest Sounds");
-+ //} // Paper end
-
- if (this.openness < 0.0F) {
- this.openness = 0.0F;
-@@ -188,6 +201,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
- }
-
- public void playSound(SoundEvent soundeffect) {
-+ if (!this.getBlockState().contains(ChestBlock.CHEST_TYPE_PROPERTY)) { return; } // Paper - this can be delayed, double check exists - Fixes GH-2074
- ChestType blockpropertychesttype = (ChestType) this.getBlockState().getValue(ChestBlock.TYPE);
-
- if (blockpropertychesttype != ChestType.LEFT) {
-@@ -226,6 +240,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
-
- ++this.openCount;
- if (this.level == null) return; // CraftBukkit
-+ doOpenLogic(); // Paper
-
- // CraftBukkit start - Call redstone event
- if (this.getBlockState().getBlock() == Blocks.TRAPPED_CHEST) {
-@@ -248,6 +263,7 @@ public class ChestBlockEntity extends RandomizableContainerBlockEntity implement
- --this.openCount;
-
- // CraftBukkit start - Call redstone event
-+ doCloseLogic(); // Paper
- if (this.getBlockState().getBlock() == Blocks.TRAPPED_CHEST) {
- int newPower = Math.max(0, Math.min(15, this.openCount));
-
-diff --git a/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java
-index b26337770e13c20f57a4e74282710ce697ac0d41..8f0477d9620ef71e10855bbca07f9b6984d5d794 100644
---- a/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java
-+++ b/src/main/java/net/minecraft/world/level/block/entity/EnderChestBlockEntity.java
-@@ -1,11 +1,12 @@
- package net.minecraft.world.level.block.entity;
-
-+import net.minecraft.server.MCUtil;
- import net.minecraft.sounds.SoundEvents;
- import net.minecraft.sounds.SoundSource;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.level.block.Blocks;
-
--public class EnderChestBlockEntity extends BlockEntity implements TickableBlockEntity {
-+public class EnderChestBlockEntity extends BlockEntity { // Paper - Remove ITickable
-
- public float openness;
- public float oOpenness;
-@@ -16,18 +17,28 @@ public class EnderChestBlockEntity extends BlockEntity implements TickableBlockE
- super(BlockEntityType.ENDER_CHEST);
- }
-
-- @Override
- public void tick() {
- if (++this.tickInterval % 20 * 4 == 0) {
- this.level.blockEvent(this.worldPosition, Blocks.ENDER_CHEST, 1, this.openCount);
- }
-
- this.oOpenness = this.openness;
-+ /* // Paper
-+ int i = this.position.getX();
-+ int j = this.position.getY();
-+ int k = this.position.getZ();
-+ float f = 0.1F;
-+ double d0;
-+ // Paper start
-+ */
-+ }
-+
-+ private void doOpenLogic() {
- int i = this.worldPosition.getX();
- int j = this.worldPosition.getY();
- int k = this.worldPosition.getZ();
-- float f = 0.1F;
- double d0;
-+ // Paper end
-
- if (this.openCount > 0 && this.openness == 0.0F) {
- double d1 = (double) i + 0.5D;
-@@ -35,28 +46,40 @@ public class EnderChestBlockEntity extends BlockEntity implements TickableBlockE
- d0 = (double) k + 0.5D;
- this.level.playSound((Player) null, d1, (double) j + 0.5D, d0, SoundEvents.ENDER_CHEST_OPEN, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F);
- }
-+ // Paper start
-+ }
-
-- if (this.openCount == 0 && this.openness > 0.0F || this.openCount > 0 && this.openness < 1.0F) {
-- float f1 = this.openness;
-+ private void doCloseLogic() {
-+ int i = this.worldPosition.getX();
-+ int j = this.worldPosition.getY();
-+ int k = this.worldPosition.getZ();
-+ double d0;
-+
-+ if (this.openCount == 0) { /* && this.a > 0.0F || this.c > 0 && this.a < 1.0F) {
-+ // Paper end
-+ float f1 = this.a;
-
-- if (this.openCount > 0) {
-- this.openness += 0.1F;
-+ if (this.c > 0) {
-+ this.a += 0.1F;
- } else {
-- this.openness -= 0.1F;
-+ this.a -= 0.1F;
- }
-
-- if (this.openness > 1.0F) {
-- this.openness = 1.0F;
-+ if (this.a > 1.0F) {
-+ this.a = 1.0F;
- }
-
- float f2 = 0.5F;
-
-- if (this.openness < 0.5F && f1 >= 0.5F) {
-+ if (this.a < 0.5F && f1 >= 0.5F) {
-+ // Paper start
-+ */
- d0 = (double) i + 0.5D;
- double d2 = (double) k + 0.5D;
-
-+ MCUtil.scheduleTask(10, () -> {
- this.level.playSound((Player) null, d0, (double) j + 0.5D, d2, SoundEvents.ENDER_CHEST_CLOSE, SoundSource.BLOCKS, 0.5F, this.level.random.nextFloat() * 0.1F + 0.9F);
-- }
-+ }, "Chest Sounds");
-
- if (this.openness < 0.0F) {
- this.openness = 0.0F;
-@@ -84,11 +107,13 @@ public class EnderChestBlockEntity extends BlockEntity implements TickableBlockE
- public void startOpen() {
- ++this.openCount;
- this.level.blockEvent(this.worldPosition, Blocks.ENDER_CHEST, 1, this.openCount);
-+ doOpenLogic(); // Paper
- }
-
- public void stopOpen() {
- --this.openCount;
- this.level.blockEvent(this.worldPosition, Blocks.ENDER_CHEST, 1, this.openCount);
-+ doCloseLogic(); // Paper
- }
-
- public boolean stillValid(Player entityhuman) {
-diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
-index 60ce75c7f94c995d3753c40bc8d1ec09b4d37b1a..ac10fb9cd4701f0f6477a86bec73cb5ac6496725 100644
---- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
-+++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java
-@@ -84,6 +84,7 @@ public abstract class StateHolder<O, S> {
- return Collections.unmodifiableCollection(this.values.keySet());
- }
-
-+ public <T extends Comparable<T>> boolean contains(Property<T> iblockstate) { return this.hasProperty(iblockstate); } // Paper - OBFHELPER
- public <T extends Comparable<T>> boolean hasProperty(Property<T> property) {
- return this.values.containsKey(property);
- }
diff --git a/patches/removed/1.17/No longer needed/0040-Send-absolute-position-the-first-time-an-entity-is-s.patch b/patches/removed/1.17/No longer needed/0040-Send-absolute-position-the-first-time-an-entity-is-s.patch
deleted file mode 100644
index d8dbfa7a24..0000000000
--- a/patches/removed/1.17/No longer needed/0040-Send-absolute-position-the-first-time-an-entity-is-s.patch
+++ /dev/null
@@ -1,110 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jedediah Smith <[email protected]>
-Date: Wed, 2 Mar 2016 23:13:07 -0600
-Subject: [PATCH] Send absolute position the first time an entity is seen
-
-Not needed anymore, packet spawn sends full position
-
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 59a5f82c9f57d760ba4959a040ce8cbf0f49e4aa..d1bc927c8b429f43de2cdad98f8b329ff4c8b4db 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -1301,10 +1301,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- private final Entity entity;
- private final int range;
- private SectionPos lastSectionPos;
-- public final Set<ServerPlayer> seenBy = Sets.newHashSet();
-+ // Paper start
-+ // Replace trackedPlayers Set with a Map. The value is true until the player receives
-+ // their first update (which is forced to have absolute coordinates), false afterward.
-+ public java.util.Map<ServerPlayer, Boolean> trackedPlayerMap = new java.util.HashMap<>();
-+ public Set<ServerPlayer> seenBy = trackedPlayerMap.keySet();
-
- public TrackedEntity(Entity entity, int i, int j, boolean flag) {
-- this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, seenBy); // CraftBukkit
-+ this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, trackedPlayerMap); // CraftBukkit // Paper
- this.entity = entity;
- this.range = i;
- this.lastSectionPos = SectionPos.of(entity);
-@@ -1386,7 +1390,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- player.entitiesToRemove.remove(Integer.valueOf(this.entity.getId()));
- // CraftBukkit end
-
-- if (flag1 && this.seenBy.add(player)) {
-+ if (flag1 && this.trackedPlayerMap.putIfAbsent(player, true) == null) { // Paper
- this.serverEntity.addPairing(player);
- }
- } else if (this.seenBy.remove(player)) {
-diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
-index 3d386627b6d3d33da76372e4a14d0c5000eb8ffc..fa6893055fa5617742bfb4b7eff60c8139395cb6 100644
---- a/src/main/java/net/minecraft/server/level/ServerEntity.java
-+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
-@@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
- import com.mojang.datafixers.util.Pair;
- import java.util.Collection;
- import java.util.Collections;
-+import java.util.HashSet;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Set;
-@@ -51,7 +52,7 @@ public class ServerEntity {
- private final Entity entity;
- private final int updateInterval;
- private final boolean trackDelta;
-- private final Consumer<Packet<?>> broadcast;
-+ private final Consumer<Packet<?>> broadcast; private Consumer<Packet<?>> getPacketConsumer() { return broadcast; } // Paper - OBFHELPER
- private long xp;
- private long yp;
- private long zp;
-@@ -66,8 +67,23 @@ public class ServerEntity {
- private boolean wasOnGround;
- // CraftBukkit start
- private final Set<ServerPlayer> trackedPlayers;
-+ // Paper start
-+ private java.util.Map<ServerPlayer, Boolean> trackedPlayerMap = null;
-+
-+ /**
-+ * Requested in https://github.com/PaperMC/Paper/issues/1537 to allow intercepting packets
-+ */
-+ public void sendPlayerPacket(ServerPlayer player, Packet packet) {
-+ player.connection.send(packet);
-+ }
-+
-+ public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer<Packet<?>> consumer, java.util.Map<ServerPlayer, Boolean> trackedPlayers) {
-+ this(worldserver, entity, i, flag, consumer, trackedPlayers.keySet());
-+ trackedPlayerMap = trackedPlayers;
-+ }
-
- public ServerEntity(ServerLevel worldserver, Entity entity, int i, boolean flag, Consumer<Packet<?>> consumer, Set<ServerPlayer> trackedPlayers) {
-+ // Paper end
- this.trackedPlayers = trackedPlayers;
- // CraftBukkit end
- this.ap = Vec3.ZERO;
-@@ -188,7 +204,25 @@ public class ServerEntity {
- }
-
- if (packet1 != null) {
-- this.broadcast.accept(packet1);
-+ // paper start
-+ if (trackedPlayerMap == null || packet1 instanceof ClientboundTeleportEntityPacket) {
-+ this.broadcast.accept((packet1));
-+ } else {
-+ ClientboundTeleportEntityPacket teleportPacket = null;
-+
-+ for (java.util.Map.Entry<ServerPlayer, Boolean> viewer : trackedPlayerMap.entrySet()) {
-+ if (viewer.getValue()) {
-+ viewer.setValue(false);
-+ if (teleportPacket == null) {
-+ teleportPacket = new ClientboundTeleportEntityPacket(this.entity);
-+ }
-+ sendPlayerPacket(viewer.getKey(), teleportPacket);
-+ } else {
-+ sendPlayerPacket(viewer.getKey(), packet1);
-+ }
-+ }
-+ }
-+ // Paper end
- }
-
- this.sendDirtyEntityData();
diff --git a/patches/removed/1.17/No longer needed/0053-Change-implementation-of-tile-entity-removal-list.patch b/patches/removed/1.17/No longer needed/0053-Change-implementation-of-tile-entity-removal-list.patch
deleted file mode 100644
index 51d0728329..0000000000
--- a/patches/removed/1.17/No longer needed/0053-Change-implementation-of-tile-entity-removal-list.patch
+++ /dev/null
@@ -1,50 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Joseph Hirschfeld <[email protected]>
-Date: Thu, 3 Mar 2016 02:39:54 -0600
-Subject: [PATCH] Change implementation of (tile)entity removal list
-
-use sets for faster removal
-
-1.17: no more unload lists
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 6c6098731752d61b5241710b075d4ffe3826daac..89472b6e8f38921db50440d0213e40ac893892f1 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -1122,7 +1122,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- }
- }
- // Spigot End
-- this.blockEntitiesToUnload.addAll(chunk.getBlockEntities().values());
-+ this.tileEntityListUnload.addAll(chunk.getBlockEntities().values());
- List[] aentityslice = chunk.getEntitySlices(); // Spigot
- int i = aentityslice.length;
-
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index e25666328dbf433b8358f2637d93b4128034bbaa..7b4475807cca0e92ea9ae6ea49a82a8634cc0ff5 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -89,7 +89,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- public final List<BlockEntity> blockEntityList = Lists.newArrayList();
- public final List<BlockEntity> tickableBlockEntities = Lists.newArrayList();
- protected final List<BlockEntity> pendingBlockEntities = Lists.newArrayList();
-- protected final List<BlockEntity> blockEntitiesToUnload = Lists.newArrayList();
-+ protected final java.util.Set<BlockEntity> tileEntityListUnload = com.google.common.collect.Sets.newHashSet();
- public final Thread thread;
- private final boolean isDebug;
- private int skyDarken;
-@@ -697,10 +697,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
-
- gameprofilerfiller.push("blockEntities");
- timings.tileEntityTick.startTiming(); // Spigot
-- if (!this.blockEntitiesToUnload.isEmpty()) {
-- this.tickableBlockEntities.removeAll(this.blockEntitiesToUnload);
-- this.blockEntityList.removeAll(this.blockEntitiesToUnload);
-- this.blockEntitiesToUnload.clear();
-+ if (!this.tileEntityListUnload.isEmpty()) {
-+ this.tickableBlockEntities.removeAll(this.tileEntityListUnload);
-+ this.blockEntityList.removeAll(this.tileEntityListUnload);
-+ this.tileEntityListUnload.clear();
- }
-
- this.updatingBlockEntities = true;
diff --git a/patches/removed/1.17/No longer needed/0087-Remove-unused-World-Tile-Entity-List.patch b/patches/removed/1.17/No longer needed/0087-Remove-unused-World-Tile-Entity-List.patch
deleted file mode 100644
index 69e6d9076a..0000000000
--- a/patches/removed/1.17/No longer needed/0087-Remove-unused-World-Tile-Entity-List.patch
+++ /dev/null
@@ -1,93 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 13 Apr 2016 00:25:28 -0400
-Subject: [PATCH] Remove unused World Tile Entity List
-
-Massive hit to performance and it is completely unnecessary.
-
-Removed during 1.17 update - no longer logically applies
-not true? blockEntityTickers and pendingBlockEntityTickers have similar logic applied to them
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index f7eddb39985072afeb79ec0cbfc084d7e84638e6..bb99d9fe5e274318d8480a6de2c45b0a57351f77 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -1715,7 +1715,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- }
-
- bufferedwriter.write(String.format("entities: %d\n", this.entitiesById.size()));
-- bufferedwriter.write(String.format("block_entities: %d\n", this.blockEntityList.size()));
-+ bufferedwriter.write(String.format("block_entities: %d\n", this.tickableBlockEntities.size())); // Paper - remove unused list
- bufferedwriter.write(String.format("block_ticks: %d\n", this.getBlockTicks().size()));
- bufferedwriter.write(String.format("fluid_ticks: %d\n", this.getLiquidTicks().size()));
- bufferedwriter.write("distance_manager: " + playerchunkmap.getDistanceManager().getDebugStatus() + "\n");
-@@ -1854,7 +1854,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
-
- private void dumpBlockEntities(Writer writer) throws IOException {
- CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("y").addColumn("z").addColumn("type").build(writer);
-- Iterator iterator = this.blockEntityList.iterator();
-+ Iterator iterator = this.tickableBlockEntities.iterator(); // Paper - remove unused list
-
- while (iterator.hasNext()) {
- BlockEntity tileentity = (BlockEntity) iterator.next();
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 89a6a0b4235cfcc1d3ad68ff59a21fa60df4508f..8f0fec38b482465285057d3fd27d456cf036f2fd 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -91,7 +91,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- public static final ResourceKey<Level> NETHER = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation("the_nether"));
- public static final ResourceKey<Level> END = ResourceKey.create(Registry.DIMENSION_REGISTRY, new ResourceLocation("the_end"));
- private static final Direction[] DIRECTIONS = Direction.values();
-- public final List<BlockEntity> blockEntityList = Lists.newArrayList();
-+ //public final List<TileEntity> tileEntityList = Lists.newArrayList(); // Paper - remove unused list
- public final List<BlockEntity> tickableBlockEntities = Lists.newArrayList();
- protected final List<BlockEntity> pendingBlockEntities = Lists.newArrayList();
- protected final java.util.Set<BlockEntity> tileEntityListUnload = com.google.common.collect.Sets.newHashSet();
-@@ -683,9 +683,9 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- }, blockEntity::getBlockPos});
- }
-
-- boolean flag = this.blockEntityList.add(blockEntity);
-+ boolean flag = true; // Paper - remove unused list
-
-- if (flag && blockEntity instanceof TickableBlockEntity) {
-+ if (flag && blockEntity instanceof TickableBlockEntity && !this.tickableBlockEntities.contains(blockEntity)) { // Paper
- this.tickableBlockEntities.add(blockEntity);
- }
-
-@@ -721,7 +721,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- timings.tileEntityTick.startTiming(); // Spigot
- if (!this.tileEntityListUnload.isEmpty()) {
- this.tickableBlockEntities.removeAll(this.tileEntityListUnload);
-- this.blockEntityList.removeAll(this.tileEntityListUnload);
-+ //this.tileEntityList.removeAll(this.tileEntityListUnload); // Paper - remove unused list
- this.tileEntityListUnload.clear();
- }
-
-@@ -781,7 +781,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- tilesThisCycle--;
- this.tickableBlockEntities.remove(tileTickPosition--);
- // Spigot end
-- this.blockEntityList.remove(tileentity);
-+ //this.tileEntityList.remove(tileentity); // Paper - remove unused list
- if (this.hasChunkAt(tileentity.getBlockPos())) {
- this.getChunkAt(tileentity.getBlockPos()).removeBlockEntity(tileentity.getBlockPos());
- }
-@@ -811,7 +811,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- this.sendBlockUpdated(tileentity1.getBlockPos(), iblockdata, iblockdata, 3);
- // CraftBukkit start
- // From above, don't screw this up - SPIGOT-1746
-- if (!this.blockEntityList.contains(tileentity1)) {
-+ if (true) { // Paper - remove unused list
- this.addBlockEntity(tileentity1);
- }
- // CraftBukkit end
-@@ -957,7 +957,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- } else {
- if (tileentity != null) {
- this.pendingBlockEntities.remove(tileentity);
-- this.blockEntityList.remove(tileentity);
-+ //this.tileEntityList.remove(tileentity); // Paper - remove unused list
- this.tickableBlockEntities.remove(tileentity);
- }
-
diff --git a/patches/removed/1.17/No longer needed/0088-Don-t-tick-Skulls-unused-code.patch b/patches/removed/1.17/No longer needed/0088-Don-t-tick-Skulls-unused-code.patch
deleted file mode 100644
index a3fc8b95d3..0000000000
--- a/patches/removed/1.17/No longer needed/0088-Don-t-tick-Skulls-unused-code.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 13 Apr 2016 00:30:10 -0400
-Subject: [PATCH] Don't tick Skulls - unused code
-
-No longer needed in 1.17
-
-diff --git a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java
-index 6a46517e4026971d8c050c685c710883b5976fa3..eebaeaccc3ba1a9ec089d84b8de6c9d36034868f 100644
---- a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java
-+++ b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java
-@@ -31,7 +31,7 @@ import net.minecraft.server.MinecraftServer;
- import net.minecraft.server.players.GameProfileCache;
- import net.minecraft.util.StringUtil;
-
--public class SkullBlockEntity extends BlockEntity implements TickableBlockEntity {
-+public class SkullBlockEntity extends BlockEntity /*implements ITickable*/ { // Paper - remove tickable
-
- @Nullable
- private static GameProfileCache profileCache;
-@@ -134,7 +134,7 @@ public class SkullBlockEntity extends BlockEntity implements TickableBlockEntity
-
- }
-
-- @Override
-+ // Paper - remove override
- public void tick() {
- BlockState iblockdata = this.getBlockState();
-
diff --git a/patches/removed/1.17/No longer needed/0092-Prevent-Fire-from-loading-chunks-wrongly-spread.patch b/patches/removed/1.17/No longer needed/0092-Prevent-Fire-from-loading-chunks-wrongly-spread.patch
deleted file mode 100644
index 6301e162a3..0000000000
--- a/patches/removed/1.17/No longer needed/0092-Prevent-Fire-from-loading-chunks-wrongly-spread.patch
+++ /dev/null
@@ -1,87 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sun, 17 Apr 2016 17:27:09 -0400
-Subject: [PATCH] Prevent Fire from loading chunks & wrongly spread
-
-This causes the nether to spam unload/reload chunks, plus overall
-bad behavior.
-
-This also stops fire from spreading to illegal locations.
-
-
-
-This shouldn't need to be included in post 1.14 versions, as blocks no longer tick without at least 1 radius
-chunk loaded.
-
-diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java
-index 700078c2fd536cc22351eadf51503efb9acd9df9..85170008de6e77cfb8e4f55ae440a8428d868af4 100644
---- a/src/main/java/net/minecraft/world/level/block/FireBlock.java
-+++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java
-@@ -134,7 +134,7 @@ public class FireBlock extends BaseFireBlock {
- BooleanProperty blockstateboolean = (BooleanProperty) FireBlock.PROPERTY_BY_DIRECTION.get(enumdirection);
-
- if (blockstateboolean != null) {
-- iblockdata1 = (BlockState) iblockdata1.setValue(blockstateboolean, this.canBurn(world.getBlockState(pos.relative(enumdirection))));
-+ iblockdata1 = (BlockState) iblockdata1.setValue(blockstateboolean, this.canBurn(world.getTypeIfLoaded(pos.relative(enumdirection)))); // Paper - prevent chunk loads
- }
- }
-
-@@ -214,6 +214,7 @@ public class FireBlock extends BaseFireBlock {
- }
-
- blockposition_mutableblockposition.setWithOffset((Vec3i) pos, l, j1, i1);
-+ if (blockposition_mutableblockposition.isInvalidYLocation() || !world.hasChunkAt(blockposition_mutableblockposition)) continue; // Paper
- int l1 = this.getFireOdds((LevelReader) world, (BlockPos) blockposition_mutableblockposition);
-
- if (l1 > 0) {
-@@ -259,10 +260,16 @@ public class FireBlock extends BaseFireBlock {
- }
-
- private void trySpread(Level world, BlockPos blockposition, int i, Random random, int j, BlockPos sourceposition) { // CraftBukkit add sourceposition
-- int k = this.getBurnOdd(world.getBlockState(blockposition));
-+ // Paper start
-+ final BlockState iblockdata = world.getTypeIfLoaded(blockposition);
-+ if (iblockdata == null) {
-+ return;
-+ }
-+ int k = this.getBurnOdd(iblockdata);
-+ // Paper end
-
- if (random.nextInt(i) < k) {
-- BlockState iblockdata = world.getBlockState(blockposition);
-+ //IBlockData iblockdata = world.getType(blockposition); // Paper
-
- // CraftBukkit start
- org.bukkit.block.Block theBlock = world.getWorld().getBlockAt(blockposition.getX(), blockposition.getY(), blockposition.getZ());
-@@ -308,7 +315,7 @@ public class FireBlock extends BaseFireBlock {
- for (int j = 0; j < i; ++j) {
- Direction enumdirection = aenumdirection[j];
-
-- if (this.canBurn(world.getBlockState(pos.relative(enumdirection)))) {
-+ if (this.canBurn(world.getTypeIfLoaded(pos.relative(enumdirection)))) { // Paper - prevent chunk loads
- return true;
- }
- }
-@@ -326,7 +333,12 @@ public class FireBlock extends BaseFireBlock {
-
- for (int k = 0; k < j; ++k) {
- Direction enumdirection = aenumdirection[k];
-- BlockState iblockdata = iworldreader.getBlockState(pos.relative(enumdirection));
-+ // Paper start
-+ BlockState iblockdata = iworldreader.getTypeIfLoaded(pos.relative(enumdirection));
-+ if (iblockdata == null) {
-+ continue;
-+ }
-+ // Paper end
-
- i = Math.max(this.getFlameOdds(iblockdata), i);
- }
-@@ -337,7 +349,7 @@ public class FireBlock extends BaseFireBlock {
-
- @Override
- protected boolean canBurn(BlockState state) {
-- return this.getFlameOdds(state) > 0;
-+ return state != null && this.getFlameOdds(state) > 0; // Paper - iblockdata can be nullable if chunk is unloaded now
- }
-
- @Override
diff --git a/patches/removed/1.17/No longer needed/0107-Fix-Double-World-Add-issues.patch b/patches/removed/1.17/No longer needed/0107-Fix-Double-World-Add-issues.patch
deleted file mode 100644
index aa8370e2b0..0000000000
--- a/patches/removed/1.17/No longer needed/0107-Fix-Double-World-Add-issues.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Tue, 21 Jun 2016 22:54:34 -0400
-Subject: [PATCH] Fix Double World Add issues
-
-Vanilla will double add Spider Jockeys to the world, so ignore already added.
-
-Also add debug if something else tries to, and abort before world gets bad state
-
-In 1.17 the entire entity state manager was rewritten. no longer applies, needs
-further information on new state manager
-
-similar check added by mojang
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0a613f94d1c796267636e1a343aeee65a49ffed5..335928d60dbfc07644ffeab366900c5e77e99d56 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -1032,6 +1032,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- // CraftBukkit start
- private boolean addEntity0(Entity entity, CreatureSpawnEvent.SpawnReason spawnReason) {
- org.spigotmc.AsyncCatcher.catchOp("entity add"); // Spigot
-+ if (entity.valid) { MinecraftServer.LOGGER.error("Attempted Double World add on " + entity, new Throwable()); return true; } // Paper
- if (entity.removed) {
- // WorldServer.LOGGER.warn("Tried to add entity {} but it was marked as removed already", EntityTypes.getName(entity.getEntityType())); // CraftBukkit
- return false;
diff --git a/patches/removed/1.17/No longer needed/0114-Chunk-registration-fixes.patch b/patches/removed/1.17/No longer needed/0114-Chunk-registration-fixes.patch
deleted file mode 100644
index 295d9a4664..0000000000
--- a/patches/removed/1.17/No longer needed/0114-Chunk-registration-fixes.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 21 Sep 2016 22:54:28 -0400
-Subject: [PATCH] Chunk registration fixes
-
-World checks and the Chunk Add logic are inconsistent on how Y > 256, < 0, is treated
-
-Keep them consistent
-
-No longer relevant in 1.17
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 335928d60dbfc07644ffeab366900c5e77e99d56..20650bfd10abfa010e71cfeede06c461d50d19a3 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -841,7 +841,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- if (entity.checkAndResetUpdateChunkPos()) {
- this.getProfiler().push("chunkCheck");
- int i = Mth.floor(entity.getX() / 16.0D);
-- int j = Mth.floor(entity.getY() / 16.0D);
-+ int j = Math.min(15, Math.max(0, Mth.floor(entity.getY() / 16.0D))); // Paper - stay consistent with chunk add/remove behavior
- int k = Mth.floor(entity.getZ() / 16.0D);
-
- if (!entity.inChunk || entity.xChunk != i || entity.yChunk != j || entity.zChunk != k) {
diff --git a/patches/removed/1.17/No longer needed/0120-Cache-user-authenticator-threads.patch b/patches/removed/1.17/No longer needed/0120-Cache-user-authenticator-threads.patch
deleted file mode 100644
index 8b7ea21b92..0000000000
--- a/patches/removed/1.17/No longer needed/0120-Cache-user-authenticator-threads.patch
+++ /dev/null
@@ -1,107 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: vemacs <[email protected]>
-Date: Wed, 23 Nov 2016 08:31:45 -0500
-Subject: [PATCH] Cache user authenticator threads
-
-
-TODO it looks like someone royally messed this one up, patch name doesn't remotely
-describe contents. Good thing this patch is no longer relevant at all
-
-no remove queue anymore
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 0597c0c3e881dd43cf91bd3088ed30dfecfe8098..175bf535066afc42de8a3f0d11c46af66f3e3e52 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -1388,7 +1388,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
- }
-
-- player.entitiesToRemove.remove(Integer.valueOf(this.entity.getId()));
-+ player.removeQueue.remove(Integer.valueOf(this.entity.getId()));
- // CraftBukkit end
-
- if (flag1 && this.trackedPlayerMap.putIfAbsent(player, true) == null) { // Paper
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index 3a2356b3e00098d100a179a05316f402390d4e9b..3cde25c2479adcc4ce3014e5ac2ec0710bffeea9 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -4,7 +4,9 @@ import com.google.common.collect.Lists;
- import com.mojang.authlib.GameProfile;
- import com.mojang.datafixers.util.Either;
- import com.mojang.serialization.DataResult;
-+import java.util.ArrayDeque; // Paper
- import java.util.Collection;
-+import java.util.Deque; // Paper
- import java.util.Iterator;
- import java.util.List;
- import java.util.Optional;
-@@ -169,7 +171,7 @@ public class ServerPlayer extends Player implements ContainerListener {
- public ServerGamePacketListenerImpl connection;
- public final MinecraftServer server;
- public final ServerPlayerGameMode gameMode;
-- public final List<Integer> entitiesToRemove = Lists.newLinkedList();
-+ public final Deque<Integer> removeQueue = new ArrayDeque<>(); // Paper
- private final PlayerAdvancements advancements;
- private final ServerStatsCounter stats;
- private float lastRecordedHealthAndAbsorption = Float.MIN_VALUE;
-@@ -544,16 +546,23 @@ public class ServerPlayer extends Player implements ContainerListener {
- this.containerMenu = this.inventoryMenu;
- }
-
-- while (!this.entitiesToRemove.isEmpty()) {
-- int i = Math.min(this.entitiesToRemove.size(), Integer.MAX_VALUE);
-+ while (!this.removeQueue.isEmpty()) {
-+ int i = Math.min(this.removeQueue.size(), Integer.MAX_VALUE);
- int[] aint = new int[i];
-- Iterator<Integer> iterator = this.entitiesToRemove.iterator();
-+ //Iterator<Integer> iterator = this.removeQueue.iterator(); // Paper
- int j = 0;
-
-- while (iterator.hasNext() && j < i) {
-+ // Paper start
-+ /* while (iterator.hasNext() && j < i) {
- aint[j++] = (Integer) iterator.next();
- iterator.remove();
-+ } */
-+
-+ Integer integer;
-+ while (j < i && (integer = this.removeQueue.poll()) != null) {
-+ aint[j++] = integer.intValue();
- }
-+ // Paper end
-
- this.connection.send(new ClientboundRemoveEntitiesPacket(aint));
- }
-@@ -1558,7 +1567,14 @@ public class ServerPlayer extends Player implements ContainerListener {
- this.lastSentHealth = -1.0F;
- this.lastSentFood = -1;
- // this.recipeBook.a((RecipeBook) entityplayer.recipeBook); // CraftBukkit
-- this.entitiesToRemove.addAll(oldPlayer.entitiesToRemove);
-+ // Paper start - Optimize remove queue - vanilla copies player objects, but CB doesn't. This method currently only
-+ // Applies to the same player, so we need to not duplicate our removal queue. The rest of this method does "resetting"
-+ // type logic so it does need to be called, maybe? This is silly.
-+ // this.removeQueue.addAll(entityplayer.removeQueue);
-+ if (this.removeQueue != oldPlayer.removeQueue) {
-+ this.removeQueue.addAll(oldPlayer.removeQueue);
-+ }
-+ // Paper end
- this.seenCredits = oldPlayer.seenCredits;
- this.enteredNetherPosition = oldPlayer.enteredNetherPosition;
- this.setShoulderEntityLeft(oldPlayer.getShoulderEntityLeft());
-@@ -1748,13 +1764,13 @@ public class ServerPlayer extends Player implements ContainerListener {
- if (entity instanceof Player) {
- this.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{entity.getId()}));
- } else {
-- this.entitiesToRemove.add((Integer) entity.getId()); // CraftBukkit - decompile error
-+ this.removeQueue.add((Integer) entity.getId()); // CraftBukkit - decompile error
- }
-
- }
-
- public void cancelRemoveEntity(Entity entity) {
-- this.entitiesToRemove.remove((Integer) entity.getId()); // CraftBukkit - decompile error
-+ this.removeQueue.remove((Integer) entity.getId()); // CraftBukkit - decompile error
- }
-
- @Override
diff --git a/patches/removed/1.17/No longer needed/0135-Add-system-property-to-disable-book-size-limits.patch b/patches/removed/1.17/No longer needed/0135-Add-system-property-to-disable-book-size-limits.patch
deleted file mode 100644
index e3adba196c..0000000000
--- a/patches/removed/1.17/No longer needed/0135-Add-system-property-to-disable-book-size-limits.patch
+++ /dev/null
@@ -1,53 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Zach Brown <[email protected]>
-Date: Sat, 13 May 2017 20:11:21 -0500
-Subject: [PATCH] Add system property to disable book size limits
-
-If anyone comes in with a watchdog crash related to books after this patch
-you will not only be publicly shamed but also made an example of.
-
-Disables the security limits on books entirely, allowing plugins AND players
-to make books with as much data as they want. Do not use this without
-limiting incoming data from packets in some other way.
-
-Removed in 1.17: It was from a different time before books were as jank as they are now. As time has gone on they've only proven to be worse and worse.
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java
-index a33dd184ea51df7e59ed08e5e2b0ea4ed9dadff5..1d94d285951faa98ff1f70c3c5330dfaa77cb691 100644
---- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java
-+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java
-@@ -42,6 +42,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta {
- static final int MAX_PAGES = 100;
- static final int MAX_PAGE_LENGTH = 320; // 256 limit + 64 characters to allow for psuedo colour codes
- static final int MAX_TITLE_LENGTH = 32;
-+ private static final boolean OVERRIDE_CHECKS = Boolean.getBoolean("disable.book-limits"); // Paper - Add override
-
- protected String title;
- protected String author;
-@@ -244,7 +245,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta {
- if (title == null) {
- this.title = null;
- return true;
-- } else if (title.length() > CraftMetaBook.MAX_TITLE_LENGTH) {
-+ } else if (title.length() > CraftMetaBook.MAX_TITLE_LENGTH && !CraftMetaBook.OVERRIDE_CHECKS) { // Paper - Add override
- return false;
- }
-
-@@ -441,7 +442,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta {
- String validatePage(String page) {
- if (page == null) {
- page = "";
-- } else if (page.length() > CraftMetaBook.MAX_PAGE_LENGTH) {
-+ } else if (page.length() > CraftMetaBook.MAX_PAGE_LENGTH && !CraftMetaBook.OVERRIDE_CHECKS) { // Paper - Add override
- page = page.substring(0, MAX_PAGE_LENGTH);
- }
- return page;
-@@ -451,7 +452,7 @@ public class CraftMetaBook extends CraftMetaItem implements BookMeta {
- // asserted: page != null
- if (this.pages == null) {
- this.pages = new ArrayList<String>();
-- } else if (this.pages.size() >= CraftMetaBook.MAX_PAGES) {
-+ } else if (this.pages.size() >= CraftMetaBook.MAX_PAGES && !CraftMetaBook.OVERRIDE_CHECKS) { // Paper - Add override
- return;
- }
- this.pages.add(page);
diff --git a/patches/removed/1.17/No longer needed/0182-Avoid-NPE-in-PathfinderGoalTempt.patch b/patches/removed/1.17/No longer needed/0182-Avoid-NPE-in-PathfinderGoalTempt.patch
deleted file mode 100644
index 54268b11fc..0000000000
--- a/patches/removed/1.17/No longer needed/0182-Avoid-NPE-in-PathfinderGoalTempt.patch
+++ /dev/null
@@ -1,20 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 29 Nov 2017 22:18:54 -0500
-Subject: [PATCH] Avoid NPE in PathfinderGoalTempt
-Not needed anymore
-similar check added by Mojang
-
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java
-index 186025458e923d153e9e47c2be147a9bb53db517..11ca6a752bac4ba4bc683bef844d204b739fab63 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/TemptGoal.java
-@@ -63,7 +63,7 @@ public class TemptGoal extends Goal {
- }
- this.target = (event.getTarget() == null) ? null : ((CraftLivingEntity) event.getTarget()).getHandle();
- }
-- return tempt;
-+ return tempt && this.target != null; // Paper - must have target - plugin might of cancelled
- // CraftBukkit end
- }
- }
diff --git a/patches/removed/1.17/No longer needed/0185-Fix-Dragon-Server-Crashes.patch b/patches/removed/1.17/No longer needed/0185-Fix-Dragon-Server-Crashes.patch
deleted file mode 100644
index 270cd5951a..0000000000
--- a/patches/removed/1.17/No longer needed/0185-Fix-Dragon-Server-Crashes.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 21 Mar 2018 20:52:07 -0400
-Subject: [PATCH] Fix Dragon Server Crashes
-
-If the dragon tries to find "ground" and hits a hole, or off edge,
-it will infinitely keep looking for non air and eventually crash.
-
-Fixed in 1.15
-
-diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java
-index df44bfce8cc492cd901dfa86331b9be7f1e13837..9eca797b4db96c5f2bb93d260f8e84077d92854a 100644
---- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java
-+++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/phases/DragonSittingFlamingPhase.java
-@@ -64,7 +64,7 @@ public class DragonSittingFlamingPhase extends AbstractDragonSittingPhase {
- double h = g;
- BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(d, g, e);
-
-- while(this.dragon.level.isEmptyBlock(mutableBlockPos)) {
-+ while(this.dragon.level.isEmptyBlock(mutableBlockPos) && g > 0) { // Paper
- --h;
- if (h < 0.0D) {
- h = g;
diff --git a/patches/removed/1.17/No longer needed/0238-Don-t-change-the-Entity-Random-seed-for-squids.patch b/patches/removed/1.17/No longer needed/0238-Don-t-change-the-Entity-Random-seed-for-squids.patch
deleted file mode 100644
index 56015aa8db..0000000000
--- a/patches/removed/1.17/No longer needed/0238-Don-t-change-the-Entity-Random-seed-for-squids.patch
+++ /dev/null
@@ -1,20 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Thu, 19 Jul 2018 01:05:00 -0400
-Subject: [PATCH] Don't change the Entity Random seed for squids
-
-
-Rebased into the patch to add the shared entity random
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java
-index 5a7582fd4f8e883d2f08a0227932c17d7576b957..2e5a35565b6b7c4d3f7fdab45095f789c33f8937 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Squid.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java
-@@ -48,7 +48,7 @@ public class Squid extends WaterAnimal {
-
- public Squid(EntityType<? extends Squid> type, Level world) {
- super(type, world);
-- this.random.setSeed((long) this.getId());
-+ //this.random.setSeed((long) this.getId()); // Paper
- this.tentacleSpeed = 1.0F / (this.random.nextFloat() + 1.0F) * 0.2F;
- }
-
diff --git a/patches/removed/1.17/No longer needed/0239-Re-add-vanilla-entity-warnings-for-duplicates.patch b/patches/removed/1.17/No longer needed/0239-Re-add-vanilla-entity-warnings-for-duplicates.patch
deleted file mode 100644
index 93a070fe0e..0000000000
--- a/patches/removed/1.17/No longer needed/0239-Re-add-vanilla-entity-warnings-for-duplicates.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Thu, 19 Jul 2018 01:08:05 -0400
-Subject: [PATCH] Re-add vanilla entity warnings for duplicates
-
-These are a critical sign that somethin went wrong, and you've lost some data....
-
-We should kind of know about these things you know.
-
-Spigot did not remove the warning in 1.17
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index ea1b15495481157912140bf5de9bf4a949c16910..914241a57c304fde220bc546261d6e959445772a 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -1071,7 +1071,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- if (entity1 == null) {
- return false;
- } else {
-- // WorldServer.LOGGER.warn("Trying to add entity with duplicated UUID {}. Existing {}#{}, new: {}#{}", uuid, EntityTypes.getName(entity1.getEntityType()), entity1.getId(), EntityTypes.getName(entity.getEntityType()), entity.getId()); // CraftBukkit
-+ ServerLevel.LOGGER.warn("Trying to add entity with duplicated UUID {}. Existing {}#{}, new: {}#{}", uuid, EntityType.getKey(entity1.getType()), entity1.getId(), EntityType.getKey(entity.getType()), entity.getId()); // CraftBukkit // Paper
- return true;
- }
- }
diff --git a/patches/removed/1.17/No longer needed/0250-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch b/patches/removed/1.17/No longer needed/0250-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch
deleted file mode 100644
index 802c1e0e25..0000000000
--- a/patches/removed/1.17/No longer needed/0250-Mark-chunk-dirty-anytime-entities-change-to-guarante.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Mon, 23 Jul 2018 22:18:31 -0400
-Subject: [PATCH] Mark chunk dirty anytime entities change to guarantee it
- saves
-
-Useless in 1.17 - leaf
-
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index d69ccb1f31f31ebeee477df20ce1410f9e485eb7..bd9b19d988ecf72e099efeff6ec3483a352174ec 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -559,6 +559,7 @@ public class LevelChunk implements ChunkAccess {
- entity.zChunk = this.chunkPos.z;
- this.entities.add(entity); // Paper - per chunk entity list
- this.entitySlices[k].add(entity);
-+ this.markUnsaved(); // Paper
- }
-
- @Override
-@@ -587,6 +588,7 @@ public class LevelChunk implements ChunkAccess {
- return;
- }
- entityCounts.decrement(entity.getMinecraftKeyString());
-+ this.markUnsaved(); // Paper
- // Paper end
- this.entities.remove(entity); // Paper
- }
diff --git a/patches/removed/1.17/No longer needed/0251-Add-some-Debug-to-Chunk-Entity-slices.patch b/patches/removed/1.17/No longer needed/0251-Add-some-Debug-to-Chunk-Entity-slices.patch
deleted file mode 100644
index 562e663109..0000000000
--- a/patches/removed/1.17/No longer needed/0251-Add-some-Debug-to-Chunk-Entity-slices.patch
+++ /dev/null
@@ -1,101 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Mon, 23 Jul 2018 22:44:23 -0400
-Subject: [PATCH] Add some Debug to Chunk Entity slices
-
-If we detect unexpected state, log and try to recover
-
-This should hopefully avoid duplicate entities ever being created
-if the entity was to end up in 2 different chunk slices
-
-Useless in 1.17
-
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index a2cc3e58d59ed3d9f443b77c44d8200cc09b4da9..7847078c54154e28ab066ea8a329f929df1e1a37 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -156,6 +156,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
- }
- }
- };
-+ public List<Entity> entitySlice = null;
- // Paper end
-
- public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData; // Paper
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index bd9b19d988ecf72e099efeff6ec3483a352174ec..09aa608bd303b618ae2c0ebd237bcbdba60a37a8 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -26,7 +26,9 @@ import net.minecraft.ReportedException;
- import net.minecraft.core.BlockPos;
- import net.minecraft.core.Registry;
- import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.server.MinecraftServer;
- import net.minecraft.server.level.ChunkHolder;
-+import net.minecraft.server.level.ServerChunkCache;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.util.Mth;
- import net.minecraft.world.entity.Entity;
-@@ -550,6 +552,25 @@ public class LevelChunk implements ChunkAccess {
- if (k >= this.entitySlices.length) {
- k = this.entitySlices.length - 1;
- }
-+ // Paper - remove from any old list if its in one
-+ List<Entity> nextSlice = this.entitySlices[k]; // the next list to be added to
-+ List<Entity> currentSlice = entity.entitySlice;
-+ if (nextSlice == currentSlice) {
-+ if (Level.DEBUG_ENTITIES) MinecraftServer.LOGGER.warn("Entity was already in this chunk!" + entity, new Throwable());
-+ return; // ??? silly plugins
-+ }
-+ if (currentSlice != null && currentSlice.contains(entity)) {
-+ // Still in an old chunk...
-+ if (Level.DEBUG_ENTITIES) MinecraftServer.LOGGER.warn("Entity is still in another chunk!" + entity, new Throwable());
-+ LevelChunk chunk = entity.getCurrentChunk();
-+ if (chunk != null) {
-+ chunk.removeEntity(entity);
-+ } else {
-+ removeEntity(entity);
-+ }
-+ currentSlice.remove(entity); // Just incase the above did not remove from the previous slice
-+ }
-+ // Paper end
-
- if (!entity.inChunk || entity.getCurrentChunk() != this) entityCounts.increment(entity.getMinecraftKeyString()); // Paper
- entity.inChunk = true;
-@@ -559,6 +580,7 @@ public class LevelChunk implements ChunkAccess {
- entity.zChunk = this.chunkPos.z;
- this.entities.add(entity); // Paper - per chunk entity list
- this.entitySlices[k].add(entity);
-+ entity.entitySlice = this.entitySlices[k]; // Paper
- this.markUnsaved(); // Paper
- }
-
-@@ -584,6 +606,10 @@ public class LevelChunk implements ChunkAccess {
-
- // Paper start
- if (entity.currentChunk != null && entity.currentChunk.get() == this) entity.setCurrentChunk(null);
-+ if (entitySlices[section] == entity.entitySlice) {
-+ entity.entitySlice = null;
-+ entity.inChunk = false;
-+ }
- if (!this.entitySlices[section].remove(entity)) {
- return;
- }
-@@ -742,7 +768,7 @@ public class LevelChunk implements ChunkAccess {
- // Paper start - neighbour cache
- int chunkX = this.chunkPos.x;
- int chunkZ = this.chunkPos.z;
-- ChunkProviderServer chunkProvider = ((ServerLevel)this.world).getChunkSource();
-+ ServerChunkCache chunkProvider = ((ServerLevel)this.world).getChunkSource();
- for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) {
- for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) {
- LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz);
-@@ -802,7 +828,7 @@ public class LevelChunk implements ChunkAccess {
- // Paper start - neighbour cache
- int chunkX = this.chunkPos.x;
- int chunkZ = this.chunkPos.z;
-- ChunkProviderServer chunkProvider = ((ServerLevel)this.world).getChunkSource();
-+ ServerChunkCache chunkProvider = ((ServerLevel)this.world).getChunkSource();
- for (int dx = -NEIGHBOUR_CACHE_RADIUS; dx <= NEIGHBOUR_CACHE_RADIUS; ++dx) {
- for (int dz = -NEIGHBOUR_CACHE_RADIUS; dz <= NEIGHBOUR_CACHE_RADIUS; ++dz) {
- LevelChunk neighbour = chunkProvider.getChunkAtIfLoadedMainThreadNoCache(chunkX + dx, chunkZ + dz);
diff --git a/patches/removed/1.17/No longer needed/0253-Prevent-Saving-Bad-entities-to-chunks.patch b/patches/removed/1.17/No longer needed/0253-Prevent-Saving-Bad-entities-to-chunks.patch
deleted file mode 100644
index bf9a9d15a4..0000000000
--- a/patches/removed/1.17/No longer needed/0253-Prevent-Saving-Bad-entities-to-chunks.patch
+++ /dev/null
@@ -1,129 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Thu, 26 Jul 2018 00:11:12 -0400
-Subject: [PATCH] Prevent Saving Bad entities to chunks
-
-See https://github.com/PaperMC/Paper/issues/1223
-
-Minecraft is saving invalid entities to the chunk files.
-
-Avoid saving bad data, and also make improvements to handle
-loading these chunks. Any invalid entity will be instant killed,
-so lets avoid adding it to the world...
-
-This lets us be safer about the dupe UUID resolver too, as now
-we can ignore instant killed entities and avoid risk of duplicating
-an invalid entity.
-
-This should reduce log occurrences of dupe uuid messages.
-
-1.17, not a concern anymore
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index a5d7781b13a6d61238d026f064512f7162e1e868..8e8e5f30c512ed7d8ee987550c22d3e9df845043 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -1151,6 +1151,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- List[] aentityslice = chunk.getEntitySlices(); // Spigot
- int i = aentityslice.length;
-
-+ java.util.List<Entity> toMoveChunks = new java.util.ArrayList<>(); // Paper
- for (int j = 0; j < i; ++j) {
- List<Entity> entityslice = aentityslice[j]; // Spigot
- Iterator iterator = entityslice.iterator();
-@@ -1163,11 +1164,25 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- throw (IllegalStateException) Util.pauseInIde((Throwable) (new IllegalStateException("Removing entity while ticking!")));
- }
-
-+ // Paper start - move out entities that shouldn't be in this chunk before it unloads
-+ if (!entity.removed && (int) Math.floor(entity.getX()) >> 4 != chunk.getPos().x || (int) Math.floor(entity.getZ()) >> 4 != chunk.getPos().z) {
-+ toMoveChunks.add(entity);
-+ continue;
-+ }
-+ // Paper end
-+
- this.entitiesById.remove(entity.getId());
- this.onEntityRemoved(entity);
-+
-+ if (entity.removed) iterator.remove(); // Paper - don't save dead entities during unload
- }
- }
- }
-+ // Paper start - move out entities that shouldn't be in this chunk before it unloads
-+ for (Entity entity : toMoveChunks) {
-+ this.updateChunkPos(entity);
-+ }
-+ // Paper end
-
- }
-
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-index 0efaf4d0f58bcf38b427e76bf09b96e354294159..542d6f322df5f44ad9f504c8e14c88e3fa540657 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-@@ -27,6 +27,7 @@ import net.minecraft.nbt.LongArrayTag;
- import net.minecraft.nbt.ShortTag;
- import net.minecraft.server.level.ServerChunkCache;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.server.level.ThreadedLevelLightEngine;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.EntityType;
-@@ -349,6 +350,7 @@ public class ChunkSerializer {
- nbttagcompound1.put("TileEntities", nbttaglist1);
- ListTag nbttaglist2 = new ListTag();
-
-+ java.util.List<Entity> toUpdate = new java.util.ArrayList<>(); // Paper
- if (chunk.getStatus().getChunkType() == ChunkStatus.ChunkType.LEVELCHUNK) {
- LevelChunk chunk1 = (LevelChunk) chunk;
-
-@@ -366,13 +368,28 @@ public class ChunkSerializer {
- while (iterator1.hasNext()) {
- Entity entity = (Entity) iterator1.next();
- CompoundTag nbttagcompound4 = new CompoundTag();
--
-+ // Paper start
-+ if ((int) Math.floor(entity.getX()) >> 4 != chunk1.getPos().x || (int) Math.floor(entity.getZ()) >> 4 != chunk1.getPos().z) {
-+ toUpdate.add(entity);
-+ continue;
-+ }
-+ if (entity.removed || hasPlayerPassenger(entity)) {
-+ continue;
-+ }
-+ // Paper end
- if (entity.save(nbttagcompound4)) {
- chunk1.setLastSaveHadEntities(true);
- nbttaglist2.add(nbttagcompound4);
- }
- }
- }
-+
-+ // Paper start - move entities to the correct chunk
-+ for (Entity entity : toUpdate) {
-+ world.updateChunkPos(entity);
-+ }
-+ // Paper end
-+
- } else {
- ProtoChunk protochunk = (ProtoChunk) chunk;
-
-@@ -431,6 +448,19 @@ public class ChunkSerializer {
- nbttagcompound1.put("Structures", packStructureData(chunkcoordintpair, chunk.getAllStarts(), chunk.getAllReferences()));
- return nbttagcompound;
- }
-+ // Paper start - this is saved with the player
-+ private static boolean hasPlayerPassenger(Entity entity) {
-+ for (Entity passenger : entity.passengers) {
-+ if (passenger instanceof ServerPlayer) {
-+ return true;
-+ }
-+ if (hasPlayerPassenger(passenger)) {
-+ return true;
-+ }
-+ }
-+ return false;
-+ }
-+ // Paper end
-
- public static ChunkStatus.ChunkType getChunkTypeFromTag(@Nullable CompoundTag tag) {
- if (tag != null) {
diff --git a/patches/removed/1.17/No longer needed/0255-Ignore-Dead-Entities-in-entityList-iteration.patch b/patches/removed/1.17/No longer needed/0255-Ignore-Dead-Entities-in-entityList-iteration.patch
deleted file mode 100644
index b80a13b2b7..0000000000
--- a/patches/removed/1.17/No longer needed/0255-Ignore-Dead-Entities-in-entityList-iteration.patch
+++ /dev/null
@@ -1,122 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 28 Jul 2018 12:18:27 -0400
-Subject: [PATCH] Ignore Dead Entities in entityList iteration
-
-A spigot change delays removal of entities from the entity list.
-This causes a change in behavior from Vanilla where getEntities type
-methods will return dead entities that they shouldn't otherwise be doing.
-
-This will ensure that dead entities are skipped from iteration since
-they shouldn't of been in the list in the first place.
-
-Not relevant in 1.17
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
-index e95b91cefb0374bd5bb57cc090f5ecd566d7a618..8fd716bf2e1402694798b8be03fd85821153be44 100644
---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
-@@ -209,6 +209,7 @@ public class PaperCommand extends Command {
- Collection<Entity> entities = world.entitiesById.values();
- entities.forEach(e -> {
- ResourceLocation key = e.getMinecraftKey();
-+ if (e.shouldBeRemoved) return; // Paper
-
- MutablePair<Integer, Map<ChunkPos, Integer>> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap()));
- ChunkPos chunk = new ChunkPos(e.xChunk, e.zChunk);
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 8e8e5f30c512ed7d8ee987550c22d3e9df845043..84b2cd661697545186677ab7966556d9288c650b 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -1303,6 +1303,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- entity.origin = entity.getBukkitEntity().getLocation();
- }
- // Paper end
-+ entity.shouldBeRemoved = false; // Paper - shouldn't be removed after being re-added
- new com.destroystokyo.paper.event.entity.EntityAddToWorldEvent(entity.getBukkitEntity()).callEvent(); // Paper - fire while valid
- }
-
-@@ -1315,6 +1316,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- this.removeFromChunk(entity);
- this.entitiesById.remove(entity.getId());
- this.onEntityRemoved(entity);
-+ entity.shouldBeRemoved = true; // Paper
- }
- }
-
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 7847078c54154e28ab066ea8a329f929df1e1a37..5bf6bc6a01ccde8a4d67b49293bb326cb09248d8 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -275,6 +275,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
- protected int numCollisions = 0; // Paper
- public void inactiveTick() { }
- // Spigot end
-+ public boolean shouldBeRemoved; // Paper
-
- public float getBukkitYaw() {
- return this.yRot;
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 09aa608bd303b618ae2c0ebd237bcbdba60a37a8..db28bfe95c885cdefa855c7aaa3bcf92bc52df26 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -862,6 +862,7 @@ public class LevelChunk implements ChunkAccess {
-
- for (int i1 = 0; i1 < l; ++i1) {
- Entity entity1 = (Entity) list1.get(i1);
-+ if (entity1.shouldBeRemoved) continue; // Paper
-
- if (entity1.getBoundingBox().intersects(box) && entity1 != except) {
- if (predicate == null || predicate.test(entity1)) {
-@@ -899,6 +900,7 @@ public class LevelChunk implements ChunkAccess {
-
- while (iterator.hasNext()) {
- T entity = (T) iterator.next(); // CraftBukkit - decompile error
-+ if (entity.shouldBeRemoved) continue; // Paper
-
- if ((type == null || entity.getType() == type) && entity.getBoundingBox().intersects(box) && predicate.test(entity)) {
- result.add(entity);
-@@ -921,6 +923,7 @@ public class LevelChunk implements ChunkAccess {
-
- while (iterator.hasNext()) {
- T t0 = (T) iterator.next(); // CraftBukkit - decompile error
-+ if (t0.shouldBeRemoved) continue; // Paper
-
- if (entityClass.isInstance(t0) && t0.getBoundingBox().intersects(box) && (predicate == null || predicate.test(t0))) { // Spigot - instance check
- result.add(t0);
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 3a3466cd9bbd34dbc0b79567f5579e84a81d6009..9807612aed6c4393cbe1f4b6078e45bf1ba3deb2 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -1022,6 +1022,7 @@ public class CraftWorld implements World {
- for (Object o : world.entitiesById.values()) {
- if (o instanceof net.minecraft.world.entity.Entity) {
- net.minecraft.world.entity.Entity mcEnt = (net.minecraft.world.entity.Entity) o;
-+ if (mcEnt.shouldBeRemoved) continue; // Paper
- Entity bukkitEntity = mcEnt.getBukkitEntity();
-
- // Assuming that bukkitEntity isn't null
-@@ -1041,6 +1042,7 @@ public class CraftWorld implements World {
- for (Object o : world.entitiesById.values()) {
- if (o instanceof net.minecraft.world.entity.Entity) {
- net.minecraft.world.entity.Entity mcEnt = (net.minecraft.world.entity.Entity) o;
-+ if (mcEnt.shouldBeRemoved) continue; // Paper
- Entity bukkitEntity = mcEnt.getBukkitEntity();
-
- // Assuming that bukkitEntity isn't null
-@@ -1067,6 +1069,7 @@ public class CraftWorld implements World {
-
- for (Object entity: world.entitiesById.values()) {
- if (entity instanceof net.minecraft.world.entity.Entity) {
-+ if (((net.minecraft.world.entity.Entity) entity).shouldBeRemoved) continue; // Paper
- Entity bukkitEntity = ((net.minecraft.world.entity.Entity) entity).getBukkitEntity();
-
- if (bukkitEntity == null) {
-@@ -1090,6 +1093,7 @@ public class CraftWorld implements World {
-
- for (Object entity: world.entitiesById.values()) {
- if (entity instanceof net.minecraft.world.entity.Entity) {
-+ if (((net.minecraft.world.entity.Entity) entity).shouldBeRemoved) continue; // Paper
- Entity bukkitEntity = ((net.minecraft.world.entity.Entity) entity).getBukkitEntity();
-
- if (bukkitEntity == null) {
diff --git a/patches/removed/1.17/No longer needed/0327-Fix-sign-edit-memory-leak.patch b/patches/removed/1.17/No longer needed/0327-Fix-sign-edit-memory-leak.patch
deleted file mode 100644
index 1c8d3052c4..0000000000
--- a/patches/removed/1.17/No longer needed/0327-Fix-sign-edit-memory-leak.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Thu, 28 Feb 2019 00:15:28 -0500
-Subject: [PATCH] Fix sign edit memory leak
-
-when a player edits a sign, a reference to their Entity is never cleand up.
-
-removed 1.17 - mojang uses a UUID instead of a player field now
-
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index b1c505d3fdcc2fb3496f80bee85e4895b9069dcb..276773e17149f57038cd21485fd9d9061670ff2d 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -2850,7 +2850,7 @@ public class ServerGamePacketListenerImpl implements ServerGamePacketListener {
-
- SignBlockEntity tileentitysign = (SignBlockEntity) tileentity;
-
-- if (!tileentitysign.isEditable() || tileentitysign.getPlayerWhoMayEdit() != this.player) {
-+ if (!tileentitysign.isEditable() || tileentitysign.signEditor == null || !tileentitysign.signEditor.equals(this.player.getUUID())) {
- ServerGamePacketListenerImpl.LOGGER.warn("Player {} just tried to change non-editable sign", this.player.getName().getString());
- this.send(tileentity.getUpdatePacket()); // CraftBukkit
- return;
-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 e4eab82855649fec654c60b2e94ba7b71c2ac5a2..0b26d3ab2db66d6baaa95d1d5f6c756595d31495 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
-@@ -30,6 +30,7 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C
- private Player playerWhoMayEdit;
- private final FormattedCharSequence[] renderMessages;
- private DyeColor color;
-+ public java.util.UUID signEditor; // Paper
-
- public SignBlockEntity() {
- super(BlockEntityType.SIGN);
-@@ -131,7 +132,10 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C
- }
-
- public void setAllowedPlayerEditor(Player player) {
-- this.playerWhoMayEdit = player;
-+ // Paper start
-+ //this.c = entityhuman;
-+ signEditor = player != null ? player.getUUID() : null;
-+ // Paper end
- }
-
- public Player getPlayerWhoMayEdit() {
diff --git a/patches/removed/1.17/No longer needed/0327-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch b/patches/removed/1.17/No longer needed/0327-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch
deleted file mode 100644
index 3781130845..0000000000
--- a/patches/removed/1.17/No longer needed/0327-MC-114618-Fix-EntityAreaEffectCloud-from-going-negat.patch
+++ /dev/null
@@ -1,27 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath <[email protected]>
-Date: Mon, 27 May 2019 17:35:39 -0500
-Subject: [PATCH] MC-114618 - Fix EntityAreaEffectCloud from going negative
- size
-
-1.17 update note: Likely fixed in 1.17
-
-fixed https://bugs.mojang.com/browse/MC-114618
-
-diff --git a/src/main/java/net/minecraft/world/entity/AreaEffectCloud.java b/src/main/java/net/minecraft/world/entity/AreaEffectCloud.java
-index 4733f74ff028c03a60b73280caf9e4d1e2f0ca30..882c216b508a8623c2393b668cff6d702fe738b9 100644
---- a/src/main/java/net/minecraft/world/entity/AreaEffectCloud.java
-+++ b/src/main/java/net/minecraft/world/entity/AreaEffectCloud.java
-@@ -197,6 +197,12 @@ public class AreaEffectCloud extends Entity {
- super.tick();
- boolean flag = this.isWaiting();
- float f = this.getRadius();
-+ // Paper start - fix MC-114618
-+ if (f < 0.0F) {
-+ this.remove();
-+ return;
-+ }
-+ // Paper end
-
- if (this.level.isClientSide) {
- if (flag && this.random.nextBoolean()) {
diff --git a/patches/removed/1.17/No longer needed/0359-Catch-exceptions-from-dispenser-entity-spawns.patch b/patches/removed/1.17/No longer needed/0359-Catch-exceptions-from-dispenser-entity-spawns.patch
deleted file mode 100644
index a560a1fd92..0000000000
--- a/patches/removed/1.17/No longer needed/0359-Catch-exceptions-from-dispenser-entity-spawns.patch
+++ /dev/null
@@ -1,33 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Mon, 10 Jun 2019 09:36:40 +0100
-Subject: [PATCH] Catch exceptions from dispenser entity spawns
-mojang(?) added similar warning
-
-diff --git a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
-index dccf689d17bb5a77abf97779663413d01e840c23..67a894a185a3d4a53b3c7f90174b2604dff18257 100644
---- a/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
-+++ b/src/main/java/net/minecraft/core/dispenser/DispenseItemBehavior.java
-@@ -8,6 +8,7 @@ import net.minecraft.core.BlockPos;
- import net.minecraft.core.BlockSource;
- import net.minecraft.core.Direction;
- import net.minecraft.core.Position;
-+import net.minecraft.server.MinecraftServer;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvents;
-@@ -235,7 +236,14 @@ public interface DispenseItemBehavior {
- }
- }
-
-+ try { // Paper
- entitytypes.spawn(pointer.getLevel(), stack, (Player) null, pointer.getPos().relative(enumdirection), MobSpawnType.DISPENSER, enumdirection != Direction.UP, false);
-+ // Paper start
-+ } catch (Exception ex){
-+ MinecraftServer.LOGGER.warn("An exception occurred dispensing entity at {}[{}]", worldserver.getWorld().getName(), pointer.getPos(), ex);
-+ }
-+ // Paper end
-+
- // itemstack.subtract(1); // Handled during event processing
- // CraftBukkit end
- return stack;
diff --git a/patches/removed/1.17/No longer needed/0367-Mark-entities-as-being-ticked-when-notifying-navigat.patch b/patches/removed/1.17/No longer needed/0367-Mark-entities-as-being-ticked-when-notifying-navigat.patch
deleted file mode 100644
index a5be725dd8..0000000000
--- a/patches/removed/1.17/No longer needed/0367-Mark-entities-as-being-ticked-when-notifying-navigat.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Sun, 28 Jul 2019 00:51:11 +0100
-Subject: [PATCH] Mark entities as being ticked when notifying navigation
-1.17: Check how this is done after rework
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 7a09bc921827958f58290bd3d6f19984bb34a8f6..a811ced17721b70bb51837f47e466c2261db2466 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -1469,6 +1469,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- VoxelShape voxelshape1 = newState.getCollisionShape(this, pos);
-
- if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) {
-+ boolean wasTicking = this.tickingEntities; this.tickingEntities = true; // Paper
- Iterator iterator = this.navigations.iterator();
-
- while (iterator.hasNext()) {
-@@ -1490,6 +1491,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- }
- }
-
-+ this.tickingEntities = wasTicking; // Paper
- }
- }
-
diff --git a/patches/removed/1.17/No longer needed/0380-Performance-improvement-for-Chunk.getEntities.patch b/patches/removed/1.17/No longer needed/0380-Performance-improvement-for-Chunk.getEntities.patch
deleted file mode 100644
index 45306b4541..0000000000
--- a/patches/removed/1.17/No longer needed/0380-Performance-improvement-for-Chunk.getEntities.patch
+++ /dev/null
@@ -1,39 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: wea_ondara <[email protected]>
-Date: Thu, 10 Oct 2019 11:29:42 +0200
-Subject: [PATCH] Performance improvement for Chunk.getEntities
-
-This patch aims to reduce performance cost used by collecting the
-entities of a chunk. Previously the entitySlices were copied into an
-extra array with List.toArray() with is a costly and unneccessary
-operation. This patch will reduce the load of plugins which for example
-implement custom moblimits and depend on Chunk.getEntities().
-
-1.17: needs to be reworked, entities not in chunk anymore
-
-no longer needed as no toArray is called during getEntities due to rewrite of entity system
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-index 74bad15034d9d55fb70931f38868f812160c6305..0f45f4b2486e910d11fd94b260bcd68e49eae31e 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-@@ -116,14 +116,14 @@ public class CraftChunk implements Chunk {
- Entity[] entities = new Entity[count];
-
- for (int i = 0; i < 16; i++) {
--
-- for (Object obj : chunk.entitySlices[i].toArray()) {
-- if (!(obj instanceof net.minecraft.world.entity.Entity)) {
-+ // Paper start - speed up (was with chunk.entitySlices[i].toArray() and cast checks which costs a lot of performance if called often)
-+ for (net.minecraft.world.entity.Entity entity : chunk.entitySlices[i]) {
-+ if (entity == null) {
- continue;
- }
--
-- entities[index++] = ((net.minecraft.world.entity.Entity) obj).getBukkitEntity();
-+ entities[index++] = entity.getBukkitEntity();
- }
-+ // Paper end
- }
-
- return entities;
diff --git a/patches/removed/1.17/No longer needed/0421-Fix-unregistering-entities-from-unloading-chunks.patch b/patches/removed/1.17/No longer needed/0421-Fix-unregistering-entities-from-unloading-chunks.patch
deleted file mode 100644
index 0f7af54c0b..0000000000
--- a/patches/removed/1.17/No longer needed/0421-Fix-unregistering-entities-from-unloading-chunks.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Tue, 31 Mar 2020 03:01:45 -0400
-Subject: [PATCH] Fix unregistering entities from unloading chunks
-
-CraftBukkit caused a regression here by making unloading chunks not
-have a ticket added and returning unloaded future.
-
-This caused entities who were killed in same tick their chunk is unloading
-to not be able to be removed from the chunk.
-
-This then results in dead entities lingering in the Chunk.
-
-Combine that with a buggy detail of the previous implementation of
-the Dupe UUID patch, then this was the likely source of the "Ghost entities"
-
-1.17: Probably not needed?
-
-no longer applies as entities not stored in chunks
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 9898d5c8fab63c576831bd416ccf1854ed077b0d..c5dc41a3cf499038bd33451a189913cd3978b230 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -1559,9 +1559,9 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl
- }
-
- private void removeFromChunk(Entity entity) {
-- ChunkAccess ichunkaccess = chunkSource.getChunkUnchecked(entity.xChunk, entity.zChunk); // CraftBukkit - SPIGOT-5228: getChunkAt won't find the entity's chunk if it has already been unloaded (i.e. if it switched to state INACCESSIBLE).
-+ LevelChunk ichunkaccess = entity.getCurrentChunk(); // Paper - getChunkAt(x,z,full,false) is broken by CraftBukkit as it won't return an unloading chunk. Use our current chunk reference as this points to what chunk they need to be removed from anyways
-
-- if (ichunkaccess instanceof LevelChunk) {
-+ if (ichunkaccess != null) { // Paper
- ((LevelChunk) ichunkaccess).removeEntity(entity);
- }
-
diff --git a/patches/removed/1.17/No longer needed/0490-Don-t-mark-null-chunk-sections-for-block-updates.patch b/patches/removed/1.17/No longer needed/0490-Don-t-mark-null-chunk-sections-for-block-updates.patch
deleted file mode 100644
index 167b17c93f..0000000000
--- a/patches/removed/1.17/No longer needed/0490-Don-t-mark-null-chunk-sections-for-block-updates.patch
+++ /dev/null
@@ -1,19 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Mariell Hoversholm <[email protected]>
-Date: Fri, 14 Aug 2020 23:41:19 +0200
-Subject: [PATCH] Don't mark null chunk sections for block updates
-
-no longer needed as the accessor to get chunksection handles null chunk sections
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 1f67c9c5f7161ea687983e7ae0ec7d259da9acd3..32bcc55ce15d832e2182d89acecd715947b1667d 100644
---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -315,6 +315,7 @@ public class ChunkHolder {
- this.a(world, blockposition, iblockdata);
- } else {
- LevelChunkSection chunksection = chunk.getSections()[j];
-+ if (chunksection == null) chunksection = new LevelChunkSection(sectionposition.getY(), chunk, world, true); // Paper - make a new chunk section if none was found
- ClientboundSectionBlocksUpdatePacket packetplayoutmultiblockchange = new ClientboundSectionBlocksUpdatePacket(sectionposition, shortset, chunksection, this.resendLight);
-
- this.broadcast(packetplayoutmultiblockchange, false);
diff --git a/patches/removed/1.17/No longer needed/0501-Fix-enderdragon-exp-dupe.patch b/patches/removed/1.17/No longer needed/0501-Fix-enderdragon-exp-dupe.patch
deleted file mode 100644
index 33515365ec..0000000000
--- a/patches/removed/1.17/No longer needed/0501-Fix-enderdragon-exp-dupe.patch
+++ /dev/null
@@ -1,32 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Fri, 12 Jun 2020 22:25:11 -0700
-Subject: [PATCH] Fix enderdragon exp dupe
-
-Properly track death stage when unloading/loading in the
-dragon
-
-1.17: Mojang fixed in 1.17(maybe before, idk)
-
-resolved by Mojang https://bugs.mojang.com/browse/MCPE-64818
-
-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 ec9436005a3a6fdfb4783d1092bb361224eb6414..b224a630f8adb1fa357c838e6b32c784aed0b15b 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
-@@ -879,6 +879,7 @@ public class EnderDragon extends Mob implements Enemy {
- public void addAdditionalSaveData(CompoundTag tag) {
- super.addAdditionalSaveData(tag);
- tag.putInt("DragonPhase", this.phaseManager.getCurrentPhase().getPhase().getId());
-+ tag.putInt("Paper.DeathTick", this.dragonDeathTime); // Paper
- }
-
- @Override
-@@ -887,6 +888,7 @@ public class EnderDragon extends Mob implements Enemy {
- if (tag.contains("DragonPhase")) {
- this.phaseManager.setPhase(EnderDragonPhase.getById(tag.getInt("DragonPhase")));
- }
-+ this.dragonDeathTime = tag.getInt("Paper.DeathTick"); // Paper
-
- }
-
diff --git a/patches/removed/1.17/No longer needed/0506-Limit-lightning-strike-effect-distance.patch b/patches/removed/1.17/No longer needed/0506-Limit-lightning-strike-effect-distance.patch
deleted file mode 100644
index f29d1e4440..0000000000
--- a/patches/removed/1.17/No longer needed/0506-Limit-lightning-strike-effect-distance.patch
+++ /dev/null
@@ -1,77 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Trigary <[email protected]>
-Date: Fri, 14 Sep 2018 17:42:08 +0200
-Subject: [PATCH] Limit lightning strike effect distance
-
-Doesnt seem to apply anymore as spigot isn't using relative distance for lightning
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index 1655bca0502e7b871de4addaa163536d86547a02..978062774c1db286bfb9b0ffdef19d880b1f249b 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -646,4 +646,26 @@ public class PaperWorldConfig {
- delayChunkUnloadsBy *= 20;
- }
- }
-+
-+ public double sqrMaxThunderDistance;
-+ public double sqrMaxLightningImpactSoundDistance;
-+ public double maxLightningFlashDistance;
-+ private void lightningStrikeDistanceLimit() {
-+ sqrMaxThunderDistance = getInt("lightning-strike-distance-limit.sound", -1);
-+ if (sqrMaxThunderDistance > 0) {
-+ sqrMaxThunderDistance *= sqrMaxThunderDistance;
-+ }
-+
-+ sqrMaxLightningImpactSoundDistance = getInt("lightning-strike-distance-limit.impact-sound", -1);
-+ if (sqrMaxLightningImpactSoundDistance < 0) {
-+ sqrMaxLightningImpactSoundDistance = 32 * 32; //Vanilla value
-+ } else {
-+ sqrMaxLightningImpactSoundDistance *= sqrMaxLightningImpactSoundDistance;
-+ }
-+
-+ maxLightningFlashDistance = getInt("lightning-strike-distance-limit.flash", -1);
-+ if (maxLightningFlashDistance < 0) {
-+ maxLightningFlashDistance = 512; // Vanilla value
-+ }
-+ }
- }
-diff --git a/src/main/java/net/minecraft/world/entity/LightningBolt.java b/src/main/java/net/minecraft/world/entity/LightningBolt.java
-index e030e7f3d8bd9fe6578df0b560a237d494ec8a01..4b0dbeded2b8a475d32f518957909d3495a4b6fc 100644
---- a/src/main/java/net/minecraft/world/entity/LightningBolt.java
-+++ b/src/main/java/net/minecraft/world/entity/LightningBolt.java
-@@ -15,7 +15,6 @@ import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.sounds.SoundEvents;
- import net.minecraft.sounds.SoundSource;
- import net.minecraft.world.Difficulty;
--import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.level.BlockGetter;
- import net.minecraft.world.level.GameRules;
- import net.minecraft.world.level.Level;
-@@ -74,6 +73,17 @@ public class LightningBolt extends Entity {
- double deltaX = this.getX() - player.getX();
- double deltaZ = this.getZ() - player.getZ();
- double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
-+ // Paper start - Limit lightning strike effect distance
-+ if (distanceSquared <= this.level.paperConfig.sqrMaxLightningImpactSoundDistance) {
-+ player.connection.send(new ClientboundSoundPacket(SoundEvents.LIGHTNING_BOLT_IMPACT,
-+ SoundSource.WEATHER, this.getX(), this.getY(), this.getZ(), 2.0f, 0.5F + this.random.nextFloat() * 0.2F));
-+ }
-+
-+ if (level.paperConfig.sqrMaxThunderDistance != -1 && distanceSquared >= level.paperConfig.sqrMaxThunderDistance) {
-+ continue;
-+ }
-+
-+ // Paper end
- if (distanceSquared > viewDistance * viewDistance) {
- double deltaLength = Math.sqrt(distanceSquared);
- double relativeX = player.getX() + (deltaX / deltaLength) * viewDistance;
-@@ -84,7 +94,7 @@ public class LightningBolt extends Entity {
- }
- }
- // CraftBukkit end
-- this.level.playSound((Player) null, this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F);
-+// this.world.playSound((EntityHuman) null, this.locX(), this.locY(), this.locZ(), SoundEffects.ENTITY_LIGHTNING_BOLT_IMPACT, SoundCategory.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F); // Paper - Limit lightning strike effect distance (the packet is now sent from inside the loop)
- }
-
- --this.life;
diff --git a/patches/removed/1.17/No longer needed/0541-Import-fastutil-classes.patch b/patches/removed/1.17/No longer needed/0541-Import-fastutil-classes.patch
deleted file mode 100644
index 13fa459a56..0000000000
--- a/patches/removed/1.17/No longer needed/0541-Import-fastutil-classes.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Mariell Hoversholm <[email protected]>
-Date: Wed, 12 Aug 2020 11:33:04 +0200
-Subject: [PATCH] Import fastutil classes
-1.17: YEET
-we use real mappings now so a class called 'it' in nms is no longer a concern
-
-diff --git a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java
-index 95e166aa63f42c675df645a56e313bdffc2e8663..05f7d4a3835536f26f741d54a0884bd43fc82967 100644
---- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java
-+++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java
-@@ -16,6 +16,7 @@ import net.minecraft.CrashReport;
- import net.minecraft.ReportedException;
- import net.minecraft.network.FriendlyByteBuf;
- import net.minecraft.world.entity.Entity;
-+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; // Paper
- import org.apache.commons.lang3.ObjectUtils;
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
-@@ -25,7 +26,7 @@ public class SynchedEntityData {
- private static final Logger LOGGER = LogManager.getLogger();
- private static final Map<Class<? extends Entity>, Integer> ENTITY_ID_POOL = Maps.newHashMap();
- private final Entity entity;
-- private final it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<SynchedEntityData.DataItem<?>> entries = new it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap<>(); // Spigot - use better map // PAIL
-+ private final Int2ObjectOpenHashMap<DataItem<?>> entries = new Int2ObjectOpenHashMap<>(); // Spigot - use better map // PAIL
- // private final ReadWriteLock lock = new ReentrantReadWriteLock(); // Spigot - not required
- private boolean isEmpty = true;
- private boolean isDirty;
diff --git a/patches/removed/1.17/No longer needed/0543-Remove-armour-stand-double-add-to-world.patch b/patches/removed/1.17/No longer needed/0543-Remove-armour-stand-double-add-to-world.patch
deleted file mode 100644
index 1516cc83cb..0000000000
--- a/patches/removed/1.17/No longer needed/0543-Remove-armour-stand-double-add-to-world.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Mariell Hoversholm <[email protected]>
-Date: Fri, 14 Aug 2020 23:59:26 +0200
-Subject: [PATCH] Remove armour stand double add to world
-1.17 Update: YEET (?)
-
-Applied by Mojang
-
-diff --git a/src/main/java/net/minecraft/world/item/ArmorStandItem.java b/src/main/java/net/minecraft/world/item/ArmorStandItem.java
-index a2dfcaac8a2a4a69e703de43be76d4fe369fd647..bed063497bb593683ea384605ae1a71a68f4fc1b 100644
---- a/src/main/java/net/minecraft/world/item/ArmorStandItem.java
-+++ b/src/main/java/net/minecraft/world/item/ArmorStandItem.java
-@@ -53,7 +53,7 @@ public class ArmorStandItem extends Item {
- return InteractionResult.FAIL;
- }
-
-- worldserver.addFreshEntityWithPassengers(entityarmorstand);
-+ // Paper - moved down
- float f = (float) Mth.floor((Mth.wrapDegrees(context.getRotation() - 180.0F) + 22.5F) / 45.0F) * 45.0F;
-
- entityarmorstand.moveTo(entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), f, 0.0F);
-@@ -63,7 +63,7 @@ public class ArmorStandItem extends Item {
- return InteractionResult.FAIL;
- }
- // CraftBukkit end
-- world.addFreshEntity(entityarmorstand);
-+ worldserver.addFreshEntityWithPassengers(entityarmorstand); // Paper - moved down
- world.playSound((Player) null, entityarmorstand.getX(), entityarmorstand.getY(), entityarmorstand.getZ(), SoundEvents.ARMOR_STAND_PLACE, SoundSource.BLOCKS, 0.75F, 0.8F);
- }
-
diff --git a/patches/removed/1.17/No longer needed/0548-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch b/patches/removed/1.17/No longer needed/0548-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch
deleted file mode 100644
index 8490ec1f46..0000000000
--- a/patches/removed/1.17/No longer needed/0548-Fix-MC-99259-Wither-Boss-Bar-doesn-t-update-until-in.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jason Penilla <[email protected]>
-Date: Thu, 20 Aug 2020 19:24:13 -0700
-Subject: [PATCH] Fix MC-99259 Wither Boss Bar doesn't update until
-1.17 Update: This issue is marked as fixed on 1.17 - yeet!
- invulnerability period is over
-
-Resolved https://bugs.mojang.com/browse/MC-99259
-
-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 edd231568b75330d0cffbecb03a7e9dbc55d5f94..1f330d852eb9b3a36570542e10a88ae065798714 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
-@@ -391,8 +391,9 @@ public class WitherBoss extends Monster implements RangedAttackMob {
- this.heal(1.0F, EntityRegainHealthEvent.RegainReason.REGEN); // CraftBukkit
- }
-
-- this.bossEvent.setPercent(this.getHealth() / this.getMaxHealth());
-+ //this.bossBattle.setProgress(this.getHealth() / this.getMaxHealth()); // Paper - Moved down
- }
-+ this.bossEvent.setPercent(this.getHealth() / this.getMaxHealth()); // Paper - Fix MC-99259 (Boss bar does not update until Wither invulnerability period ends)
- }
-
- public static boolean canDestroy(BlockState block) {
diff --git a/patches/removed/1.17/No longer needed/0549-Fix-MC-197271.patch b/patches/removed/1.17/No longer needed/0549-Fix-MC-197271.patch
deleted file mode 100644
index c82b3a6ca3..0000000000
--- a/patches/removed/1.17/No longer needed/0549-Fix-MC-197271.patch
+++ /dev/null
@@ -1,53 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: ishland <[email protected]>
-Date: Sun, 23 Aug 2020 10:57:44 +0200
-Subject: [PATCH] Fix MC-197271
-Update 1.17: Fixed in openj9-0.23.0-m2 release
-This patch only fixes an issue for servers running OpenJ9.
-
-resolved https://bugs.mojang.com/browse/MC-197271
-
-diff --git a/src/main/java/net/minecraft/data/BuiltinRegistries.java b/src/main/java/net/minecraft/data/BuiltinRegistries.java
-index d64cebb4431664762a14670c7d9d782dd7894ed5..0c403ea85f7ea20f2f978e06313f8675abf204b6 100644
---- a/src/main/java/net/minecraft/data/BuiltinRegistries.java
-+++ b/src/main/java/net/minecraft/data/BuiltinRegistries.java
-@@ -48,11 +48,11 @@ public class BuiltinRegistries {
- public static final Registry<StructureProcessorList> PROCESSOR_LIST = registerSimple(Registry.PROCESSOR_LIST_REGISTRY, () -> {
- return ProcessorLists.b;
- });
-- public static final Registry<StructureTemplatePool> TEMPLATE_POOL = registerSimple(Registry.TEMPLATE_POOL_REGISTRY, Pools::bootstrap);
-+ public static final Registry<StructureTemplatePool> TEMPLATE_POOL = registerSimple(Registry.TEMPLATE_POOL_REGISTRY, () -> Pools.bootstrap()); // Paper - MC-197271
- public static final Registry<Biome> BIOME = registerSimple(Registry.BIOME_REGISTRY, () -> {
- return Biomes.PLAINS;
- });
-- public static final Registry<NoiseGeneratorSettings> NOISE_GENERATOR_SETTINGS = registerSimple(Registry.NOISE_GENERATOR_SETTINGS_REGISTRY, NoiseGeneratorSettings::bootstrap);
-+ public static final Registry<NoiseGeneratorSettings> NOISE_GENERATOR_SETTINGS = registerSimple(Registry.NOISE_GENERATOR_SETTINGS_REGISTRY, () -> NoiseGeneratorSettings.bootstrap()); // Paper - MC-197271
-
- private static <T> Registry<T> registerSimple(ResourceKey<? extends Registry<T>> registryRef, Supplier<T> defaultValueSupplier) {
- return registerSimple(registryRef, Lifecycle.stable(), defaultValueSupplier);
-@@ -66,9 +66,9 @@ public class BuiltinRegistries {
- ResourceLocation minecraftkey = registryRef.location();
-
- BuiltinRegistries.LOADERS.put(minecraftkey, defaultValueSupplier);
-- WritableRegistry<R> iregistrywritable = BuiltinRegistries.WRITABLE_REGISTRY;
-+ WritableRegistry<R> iregistrywritable = (WritableRegistry<R>) BuiltinRegistries.WRITABLE_REGISTRY; // Paper - decompile fix
-
-- return (WritableRegistry) iregistrywritable.register(registryRef, (Object) registry, lifecycle);
-+ return (R) iregistrywritable.register((ResourceKey<R>) registryRef, registry, lifecycle); // Paper - decompile fix
- }
-
- public static <T> T register(Registry<? super T> registry, String id, T object) {
-@@ -76,11 +76,11 @@ public class BuiltinRegistries {
- }
-
- public static <V, T extends V> T register(Registry<V> registry, ResourceLocation id, T object) {
-- return ((WritableRegistry) registry).register(ResourceKey.create(registry.key(), id), object, Lifecycle.stable());
-+ return (T) ((WritableRegistry) registry).register(ResourceKey.create(registry.key(), id), object, Lifecycle.stable()); // Paper - decompile fix
- }
-
- public static <V, T extends V> T registerMapping(Registry<V> iregistry, int rawId, ResourceKey<V> resourcekey, T object) {
-- return ((WritableRegistry) iregistry).registerMapping(rawId, resourcekey, object, Lifecycle.stable());
-+ return (T) ((WritableRegistry) iregistry).registerMapping(rawId, resourcekey, object, Lifecycle.stable()); // Paper - decompile fix
- }
-
- public static void bootstrap() {}
diff --git a/patches/removed/1.17/No longer needed/0550-MC-197883-Bandaid-decode-issue.patch b/patches/removed/1.17/No longer needed/0550-MC-197883-Bandaid-decode-issue.patch
deleted file mode 100644
index be32d8a82f..0000000000
--- a/patches/removed/1.17/No longer needed/0550-MC-197883-Bandaid-decode-issue.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Fri, 21 Aug 2020 21:05:28 -0400
-Subject: [PATCH] MC-197883: Bandaid decode issue
-1.17 Update: Marked as fixed in 1.17 on mojira, yeet
-Mojang has a mix of type and name in the data sets, but you can only
-use one.
-
-This will retry as name if type is asked for and not found.
-
-resolved https://bugs.mojang.com/browse/MC-197883
-
-diff --git a/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java b/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java
-index de7d1e5e0319c65775d932144c268c2d55bb7dc7..bd6a0e1b5454e880a4f2a16be7dc8da64b73e11d 100644
---- a/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java
-+++ b/src/main/java/com/mojang/serialization/codecs/KeyDispatchCodec.java
-@@ -48,7 +48,12 @@ public class KeyDispatchCodec<K, V> extends MapCodec<V> {
-
- @Override
- public <T> DataResult<V> decode(final DynamicOps<T> ops, final MapLike<T> input) {
-- final T elementName = input.get(typeKey);
-+ // Paper start - bandaid MC-197883
-+ T elementName = input.get(typeKey);
-+ if (elementName == null && "type".equals(typeKey)) {
-+ elementName = input.get("name");
-+ }
-+ // Paper end
- if (elementName == null) {
- return DataResult.error("Input does not contain a key [" + typeKey + "]: " + input);
- }
diff --git a/patches/removed/1.17/No longer needed/0551-Add-warning-for-servers-not-running-on-Java-16.patch b/patches/removed/1.17/No longer needed/0551-Add-warning-for-servers-not-running-on-Java-16.patch
deleted file mode 100644
index bd9e79abf5..0000000000
--- a/patches/removed/1.17/No longer needed/0551-Add-warning-for-servers-not-running-on-Java-16.patch
+++ /dev/null
@@ -1,73 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Kyle Wood <[email protected]>
-Date: Wed, 2 Dec 2020 21:58:45 -0800
-Subject: [PATCH] Add warning for servers not running on Java 16
-
-1.17: game requires java 16
-
-diff --git a/src/main/java/io/papermc/paper/util/PaperJvmChecker.java b/src/main/java/io/papermc/paper/util/PaperJvmChecker.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..fdf3ff8894e5e202229d1be52fe3c92ea039ef15
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/util/PaperJvmChecker.java
-@@ -0,0 +1,48 @@
-+package io.papermc.paper.util;
-+
-+import org.apache.logging.log4j.LogManager;
-+import org.apache.logging.log4j.Logger;
-+
-+import java.util.regex.Matcher;
-+import java.util.regex.Pattern;
-+
-+public class PaperJvmChecker {
-+
-+ private static int getJvmVersion() {
-+ String javaVersion = System.getProperty("java.version");
-+ final Matcher matcher = Pattern.compile("(?:1\\.)?(\\d+)").matcher(javaVersion);
-+ if (!matcher.find()) {
-+ LogManager.getLogger().warn("Failed to determine Java version; Could not parse: {}", javaVersion);
-+ return -1;
-+ }
-+
-+ final String version = matcher.group(1);
-+ try {
-+ return Integer.parseInt(version);
-+ } catch (final NumberFormatException e) {
-+ LogManager.getLogger().warn("Failed to determine Java version; Could not parse {} from {}", version, javaVersion, e);
-+ return -1;
-+ }
-+ }
-+
-+ public static void checkJvm() {
-+ if (getJvmVersion() < 16) {
-+ final Logger logger = LogManager.getLogger();
-+ logger.warn("************************************************************");
-+ logger.warn("* WARNING - YOU ARE RUNNING AN OUTDATED VERSION OF JAVA.");
-+ logger.warn("* PAPER WILL STOP BEING COMPATIBLE WITH THIS VERSION OF");
-+ logger.warn("* JAVA WHEN MINECRAFT 1.17 IS RELEASED.");
-+ logger.warn("*");
-+ logger.warn("* Please update the version of Java you use to run Paper");
-+ logger.warn("* to at least Java 16. When Paper for Minecraft 1.17 is");
-+ logger.warn("* released support for versions of Java before 16 will");
-+ logger.warn("* be dropped.");
-+ logger.warn("*");
-+ logger.warn("* Current Java version: {}", System.getProperty("java.version"));
-+ logger.warn("*");
-+ logger.warn("* Check this forum post for more information: ");
-+ logger.warn("* https://papermc.io/java16");
-+ logger.warn("************************************************************");
-+ }
-+ }
-+}
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 9bd2255d31bcfd4574f8d1caf598f9141aa9e3c1..c7432ccffc024f171a2868b4eb0dca4860b7f8c4 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -1128,6 +1128,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- LOGGER.info("Done ({})! For help, type \"help\"", doneTime);
- // Paper end
-
-+ io.papermc.paper.util.PaperJvmChecker.checkJvm(); // Paper jvm version nag
- org.spigotmc.WatchdogThread.tick(); // Paper
- org.spigotmc.WatchdogThread.hasStarted = true; // Paper
- Arrays.fill( recentTps, 20 );
diff --git a/patches/removed/1.17/No longer needed/0579-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch b/patches/removed/1.17/No longer needed/0579-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch
deleted file mode 100644
index d54558fb30..0000000000
--- a/patches/removed/1.17/No longer needed/0579-MC-147729-Drop-items-that-are-extra-from-a-crafting-.patch
+++ /dev/null
@@ -1,25 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: chickeneer <[email protected]>
-Date: Wed, 18 Mar 2020 00:07:46 -0500
-Subject: [PATCH] MC-147729: Drop items that are extra from a crafting recipe
-
-1.17: Issue seems to be fixed (source: Mojira) https://bugs.mojang.com/browse/MC-147729
-
-diff --git a/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java b/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java
-index a18aa176850bef45afcaf5742e9afbfa39281e22..c6ba6aabf94c26cccbd14689ea32373c17bbccc4 100644
---- a/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java
-+++ b/src/main/java/net/minecraft/recipebook/ServerPlaceRecipe.java
-@@ -71,7 +71,12 @@ public class ServerPlaceRecipe<C extends Container> implements PlaceRecipe<Integ
- if (j == -1) {
- j = this.inventory.getFreeSlot();
- }
--
-+ // Paper start
-+ if (j == -1) {
-+ this.inventory.player.drop(itemstack.copy(), false);
-+ break;
-+ }
-+ // Paper end
- ItemStack itemstack1 = itemstack.copy();
-
- itemstack1.setCount(1);
diff --git a/patches/removed/1.17/No longer needed/0679-do-not-create-unnecessary-copies-of-passenger-list.patch b/patches/removed/1.17/No longer needed/0679-do-not-create-unnecessary-copies-of-passenger-list.patch
deleted file mode 100644
index cb84c8997d..0000000000
--- a/patches/removed/1.17/No longer needed/0679-do-not-create-unnecessary-copies-of-passenger-list.patch
+++ /dev/null
@@ -1,240 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: lukas81298 <[email protected]>
-Date: Sun, 13 Dec 2020 13:42:55 +0100
-Subject: [PATCH] do not create unnecessary copies of passenger list
-
-1.17: Mojang removed the copy of the passenger list from getPassengers, no longer needed
-
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPassengersPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPassengersPacket.java
-index a6ecb82d14ccab5d8229689a2a6cb67c579b1f71..cded79352dff0978e0d633eae9d9020b4dec1d4b 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPassengersPacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSetPassengersPacket.java
-@@ -15,7 +15,7 @@ public class ClientboundSetPassengersPacket implements Packet<ClientGamePacketLi
-
- public ClientboundSetPassengersPacket(Entity entity) {
- this.vehicle = entity.getId();
-- List<Entity> list = entity.getPassengers();
-+ List<Entity> list = entity.passengers; // Paper - do not create a copy of the list
-
- this.passengers = new int[list.size()];
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index f4dd30c8b3326db72d3b3068ee2291de6f15de7c..c17e827a976f509c8294df65335f12139cd36a9f 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -2312,7 +2312,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
- list.add(entity);
- }
-
-- if (!entity.getPassengers().isEmpty()) {
-+ if (!entity.passengers.isEmpty()) { // Paper - do not copy list
- list1.add(entity);
- }
- }
-diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
-index 75e2274578c2c28de3d786372df0b4102337a2cc..e703233db7879c73378b3a06b2e89f7fcea97979 100644
---- a/src/main/java/net/minecraft/server/level/ServerEntity.java
-+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
-@@ -102,10 +102,10 @@ public class ServerEntity {
-
- public final void tick() { this.sendChanges(); } // Paper - OBFHELPER
- public void sendChanges() {
-- List<Entity> list = this.entity.getPassengers();
-+ List<Entity> list = this.entity.passengers; // Paper - do not copy list
-
- if (!list.equals(this.lastPassengers)) {
-- this.lastPassengers = list;
-+ this.lastPassengers = com.google.common.collect.ImmutableList.copyOf(list); // Paper - only copy list if something has changed
- this.broadcastAndSend(new ClientboundSetPassengersPacket(this.entity)); // CraftBukkit
- }
-
-@@ -375,7 +375,7 @@ public class ServerEntity {
- }
- }
-
-- if (!this.entity.getPassengers().isEmpty()) {
-+ if (!this.entity.passengers.isEmpty()) { // Paper - do not create copy of list
- consumer.accept(new ClientboundSetPassengersPacket(this.entity));
- }
-
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index ec30f886585d407fbd122e05107ebca44895c585..d055b362459e5b4658aa220e16118ee6174c0de4 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -2233,7 +2233,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
- }
-
- protected boolean canAddPassenger(Entity passenger) {
-- return this.getPassengers().size() < 1;
-+ return this.passengers.size() < 1; // Paper - do not copy list
- }
-
- public final float getCollisionBorderSize() { return getPickRadius(); } // Paper - OBFHELPER
-@@ -2329,7 +2329,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
- }
-
- public boolean isVehicle() {
-- return !this.getPassengers().isEmpty();
-+ return !this.passengers.isEmpty(); // Paper - do not copy list
- }
-
- public boolean rideableUnderWater() {
-@@ -3141,7 +3141,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
- }
-
- public boolean hasPassenger(Entity passenger) {
-- Iterator iterator = this.getPassengers().iterator();
-+ Iterator iterator = this.passengers.iterator(); // Paper - do not copy list
-
- Entity entity1;
-
-@@ -3157,7 +3157,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
- }
-
- public boolean hasPassenger(Class<? extends Entity> clazz) {
-- Iterator iterator = this.getPassengers().iterator();
-+ Iterator iterator = this.passengers.iterator(); // Paper - do not copy list
-
- Entity entity;
-
-@@ -3174,7 +3174,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
-
- public Collection<Entity> getIndirectPassengers() {
- Set<Entity> set = Sets.newHashSet();
-- Iterator iterator = this.getPassengers().iterator();
-+ Iterator iterator = this.passengers.iterator(); // Paper - do not copy list
-
- while (iterator.hasNext()) {
- Entity entity = (Entity) iterator.next();
-@@ -3200,7 +3200,7 @@ public abstract class Entity implements Nameable, CommandSource, net.minecraft.s
- private void fillIndirectPassengers(boolean playersOnly, Set<Entity> output) {
- Entity entity;
-
-- for (Iterator iterator = this.getPassengers().iterator(); iterator.hasNext(); entity.fillIndirectPassengers(playersOnly, output)) {
-+ for (Iterator iterator = this.passengers.iterator(); iterator.hasNext(); entity.fillIndirectPassengers(playersOnly, output)) { // Paper - do not copy list
- entity = (Entity) iterator.next();
- if (!playersOnly || ServerPlayer.class.isAssignableFrom(entity.getClass())) {
- output.add(entity);
-diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
-index 3d919e878908e19d598d70011c44cf980676f4f8..debf53a8bf6f062a237160a7b7e0a251a9756ef6 100644
---- a/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/goal/RunAroundLikeCrazyGoal.java
-@@ -52,7 +52,7 @@ public class RunAroundLikeCrazyGoal extends Goal {
- @Override
- public void tick() {
- if (!this.horse.isTamed() && this.horse.getRandom().nextInt(50) == 0) {
-- Entity entity = (Entity) this.horse.getPassengers().get(0);
-+ Entity entity = this.horse.passengers.isEmpty() ? null : this.horse.passengers.get(0); // Paper - do not copy list, fixed array out of bounds exception as well
-
- if (entity == null) {
- return;
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Pig.java b/src/main/java/net/minecraft/world/entity/animal/Pig.java
-index e512a38ccbba93266f0234e3b2fcf7f62693039b..7a60c0b2c301e8cb768c39ad20f273a5921428cb 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Pig.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Pig.java
-@@ -85,7 +85,7 @@ public class Pig extends Animal implements ItemSteerable, Saddleable {
- @Nullable
- @Override
- public Entity getControllingPassenger() {
-- return this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0);
-+ return this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list
- }
-
- @Override
-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 b298bcfb665b1036cd21445cec1518069eb08f06..5901e92a749af50166c517bda575d541554756f5 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
-@@ -971,7 +971,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener,
- @Nullable
- @Override
- public Entity getControllingPassenger() {
-- return this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0);
-+ return this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list
- }
-
- @Nullable
-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 e50d72c98f2ee3cd3349d2df9a0cdc47b733f7cd..ccc9d941b28ee090436a5958e1b48589d48d9d6f 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Ravager.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Ravager.java
-@@ -134,7 +134,7 @@ public class Ravager extends Raider {
- @Nullable
- @Override
- public Entity getControllingPassenger() {
-- return this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0);
-+ return this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list
- }
-
- @Override
-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 25df3ef6b96bec39847a732394af8eccdb4d5d45..23421c4964c67a963a55ce08595c8de112a2ba6e 100644
---- a/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java
-+++ b/src/main/java/net/minecraft/world/entity/vehicle/AbstractMinecart.java
-@@ -560,7 +560,7 @@ public abstract class AbstractMinecart extends Entity {
-
- vec3d1 = new Vec3(d8 * d4 / d6, vec3d1.y, d8 * d5 / d6);
- this.setDeltaMovement(vec3d1);
-- Entity entity = this.getPassengers().isEmpty() ? null : (Entity) this.getPassengers().get(0);
-+ Entity entity = this.passengers.isEmpty() ? null : (Entity) this.passengers.get(0); // Paper - do not copy list
-
- if (entity instanceof Player) {
- Vec3 vec3d2 = entity.getDeltaMovement();
-diff --git a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java
-index e7ac3bff190c899397d6576fabbf4966878ea7e5..37f0e359ec858eebfa15d01f23a9ce0103816c8b 100644
---- a/src/main/java/net/minecraft/world/entity/vehicle/Boat.java
-+++ b/src/main/java/net/minecraft/world/entity/vehicle/Boat.java
-@@ -316,7 +316,7 @@ public class Boat extends Entity {
- super.tick();
- this.tickLerp();
- if (this.isControlledByLocalInstance()) {
-- if (this.getPassengers().isEmpty() || !(this.getPassengers().get(0) instanceof Player)) {
-+ if (this.passengers.isEmpty() || !(this.passengers.get(0) instanceof Player)) { // Paper - do not copy list
- this.setPaddleState(false, false);
- }
-
-@@ -379,7 +379,7 @@ public class Boat extends Entity {
- Entity entity = (Entity) list.get(j);
-
- if (!entity.hasPassenger(this)) {
-- if (flag && this.getPassengers().size() < 2 && !entity.isPassenger() && entity.getBbWidth() < this.getBbWidth() && entity instanceof LivingEntity && !(entity instanceof WaterAnimal) && !(entity instanceof Player)) {
-+ if (flag && this.passengers.size() < 2 && !entity.isPassenger() && entity.getBbWidth() < this.getBbWidth() && entity instanceof LivingEntity && !(entity instanceof WaterAnimal) && !(entity instanceof Player)) { // Paper - do not copy passenger list
- entity.startRiding(this);
- } else {
- this.push(entity);
-@@ -726,8 +726,8 @@ public class Boat extends Entity {
- float f = 0.0F;
- float f1 = (float) ((this.removed ? 0.009999999776482582D : this.getPassengersRidingOffset()) + passenger.getMyRidingOffset());
-
-- if (this.getPassengers().size() > 1) {
-- int i = this.getPassengers().indexOf(passenger);
-+ if (this.passengers.size() > 1) { // Paper - do not copy list
-+ int i = this.passengers.indexOf(passenger); // Paper - do not copy list
-
- if (i == 0) {
- f = 0.2F;
-@@ -746,7 +746,7 @@ public class Boat extends Entity {
- passenger.yRot += this.deltaRotation;
- passenger.setYHeadRot(passenger.getYHeadRot() + this.deltaRotation);
- this.clampRotation(passenger);
-- if (passenger instanceof Animal && this.getPassengers().size() > 1) {
-+ if (passenger instanceof Animal && this.passengers.size() > 1) { // Paper - do not copy list
- int j = passenger.getId() % 2 == 0 ? 90 : 270;
-
- passenger.setYBodyRot(((Animal) passenger).yBodyRot + (float) j);
-@@ -906,13 +906,13 @@ public class Boat extends Entity {
-
- @Override
- protected boolean canAddPassenger(Entity passenger) {
-- return this.getPassengers().size() < 2 && !this.isEyeInFluid((Tag) FluidTags.WATER);
-+ return this.passengers.size() < 2 && !this.isEyeInFluid((Tag) FluidTags.WATER); // Paper - do not copy list
- }
-
- @Nullable
- @Override
- public Entity getControllingPassenger() {
-- List<Entity> list = this.getPassengers();
-+ List<Entity> list = this.passengers; // Paper - do not copy list
-
- return list.isEmpty() ? null : (Entity) list.get(0);
- }
diff --git a/patches/removed/1.17/No longer needed/0706-don-t-throw-when-loading-invalid-TEs.patch b/patches/removed/1.17/No longer needed/0706-don-t-throw-when-loading-invalid-TEs.patch
deleted file mode 100644
index da8d3dfaa5..0000000000
--- a/patches/removed/1.17/No longer needed/0706-don-t-throw-when-loading-invalid-TEs.patch
+++ /dev/null
@@ -1,34 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Tue, 20 Apr 2021 01:15:04 +0100
-Subject: [PATCH] don't throw when loading invalid TEs
-
-1.17: Mojang catches the exception
-
-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 05fa76c02ce61e26891ad995fe89e925ea086557..b7ebb213efd759253f0042f77e11f2a8102ea6ca 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
-@@ -2,6 +2,7 @@ package net.minecraft.world.level.block.entity;
-
- import javax.annotation.Nullable;
- import net.minecraft.CrashReportCategory;
-+import net.minecraft.ResourceLocationException;
- import net.minecraft.core.BlockPos;
- import net.minecraft.core.Registry;
- import net.minecraft.nbt.CompoundTag;
-@@ -133,7 +134,13 @@ public abstract class BlockEntity implements net.minecraft.server.KeyedObject {
- public static BlockEntity loadStatic(BlockState state, CompoundTag tag) {
- String s = tag.getString("id");
-
-- return (BlockEntity) Registry.BLOCK_ENTITY_TYPE.getOptional(new ResourceLocation(s)).map((tileentitytypes) -> {
-+ // Paper
-+ ResourceLocation minecraftKey = null;
-+ try {
-+ minecraftKey = new ResourceLocation(s);
-+ } catch (ResourceLocationException ex) {}
-+ // Paper end
-+ return (BlockEntity) Registry.BLOCK_ENTITY_TYPE.getOptional(minecraftKey).map((tileentitytypes) -> {
- try {
- return tileentitytypes.create();
- } catch (Throwable throwable) {
diff --git a/patches/removed/1.17/No longer needed/0739-Fix-MC-148809-Increase-structure-block-data-length-t.patch b/patches/removed/1.17/No longer needed/0739-Fix-MC-148809-Increase-structure-block-data-length-t.patch
deleted file mode 100644
index 58711e1c47..0000000000
--- a/patches/removed/1.17/No longer needed/0739-Fix-MC-148809-Increase-structure-block-data-length-t.patch
+++ /dev/null
@@ -1,20 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: SamB440 <[email protected]>
-Date: Fri, 21 May 2021 00:22:09 +0100
-Subject: [PATCH] Fix MC-148809: Increase structure block data length to 128
-
-Fixed in 1.17 - mojira issue marked as resolved
-
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundSetStructureBlockPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundSetStructureBlockPacket.java
-index 4c797dd82bb1989861e350a7e628eb847b58bbd8..4792aafd8d992cd64d05f8bbef5cbf30988949ed 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ServerboundSetStructureBlockPacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundSetStructureBlockPacket.java
-@@ -43,7 +43,7 @@ public class ServerboundSetStructureBlockPacket implements Packet<ServerGamePack
- this.size = new BlockPos(Mth.clamp(buf.readByte(), 0, 48), Mth.clamp(buf.readByte(), 0, 48), Mth.clamp(buf.readByte(), 0, 48));
- this.mirror = (Mirror) buf.readEnum(Mirror.class);
- this.rotation = (Rotation) buf.readEnum(Rotation.class);
-- this.data = buf.readUtf(12);
-+ this.data = buf.readUtf(128); // Paper - Fix MC-148809, increase max character input to 128
- this.integrity = Mth.clamp(buf.readFloat(), 0.0F, 1.0F);
- this.seed = buf.readVarLong();
- byte b0 = buf.readByte();
diff --git a/patches/removed/1.17/state.txt b/patches/removed/1.17/state.txt
deleted file mode 100644
index 6b0cb60cde..0000000000
--- a/patches/removed/1.17/state.txt
+++ /dev/null
@@ -1 +0,0 @@
-Patch 10 depends on Patch 9.
diff --git a/patches/removed/1.18.2/0025-Remove-invalid-mob-spawner-tile-entities.patch b/patches/removed/1.18.2/0025-Remove-invalid-mob-spawner-tile-entities.patch
deleted file mode 100644
index 7a4832a901..0000000000
--- a/patches/removed/1.18.2/0025-Remove-invalid-mob-spawner-tile-entities.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Byteflux <[email protected]>
-Date: Tue, 1 Mar 2016 15:08:03 -0600
-Subject: [PATCH] Remove invalid mob spawner tile entities
-
-Minecraft checks if the block is able to hold a tile entity higher up, preventing them from ever
-being added to the world.
-
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 5496c9ae4af9658164098356532da47351808b51..2ca41f90b6b50c254a6e34da00f287197c81d65e 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -543,6 +543,11 @@ public class LevelChunk extends ChunkAccess {
- }
-
- // CraftBukkit start
-+ // Paper start - Remove invalid mob spawner tile entities
-+ } else if (blockEntity instanceof net.minecraft.world.level.block.entity.SpawnerBlockEntity
-+ && !(getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SpawnerBlock)) {
-+ this.removeBlockEntity(blockEntity.getBlockPos());
-+ // Paper end
- } else {
- System.out.println("Attempted to place a tile entity (" + blockEntity + ") at " + blockEntity.getBlockPos().getX() + "," + blockEntity.getBlockPos().getY() + "," + blockEntity.getBlockPos().getZ()
- + " (" + this.getBlockState(blockposition) + ") where there was no entity tile!");
diff --git a/patches/removed/1.18.2/0085-Workaround-for-setting-passengers-on-players.patch b/patches/removed/1.18.2/0085-Workaround-for-setting-passengers-on-players.patch
deleted file mode 100644
index 369fbe153a..0000000000
--- a/patches/removed/1.18.2/0085-Workaround-for-setting-passengers-on-players.patch
+++ /dev/null
@@ -1,29 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Zach Brown <[email protected]>
-Date: Sun, 10 Apr 2016 03:23:32 -0500
-Subject: [PATCH] Workaround for setting passengers on players
-
-SPIGOT-1915 & GH-114
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-index af277ce2e2d2baff298dffd4b0f9d2ee146b3c9a..796060044c0b8e479e93a35de886b31aab24e1b4 100644
---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-@@ -950,6 +950,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
- return true;
- }
-
-+ // Paper start - Ugly workaround for SPIGOT-1915 & GH-114
-+ @Override
-+ public boolean setPassenger(org.bukkit.entity.Entity passenger) {
-+ boolean wasSet = super.setPassenger(passenger);
-+ if (wasSet) {
-+ this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundSetPassengersPacket(this.getHandle()));
-+ }
-+ return wasSet;
-+ }
-+ // Paper end
-+
- @Override
- public void setSneaking(boolean sneak) {
- this.getHandle().setShiftKeyDown(sneak);
diff --git a/patches/removed/1.18.2/0108-Remove-FishingHook-reference-on-Craft-Entity-removal.patch b/patches/removed/1.18.2/0108-Remove-FishingHook-reference-on-Craft-Entity-removal.patch
deleted file mode 100644
index f9a25cfce3..0000000000
--- a/patches/removed/1.18.2/0108-Remove-FishingHook-reference-on-Craft-Entity-removal.patch
+++ /dev/null
@@ -1,27 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Thu, 16 Jun 2016 00:17:23 -0400
-Subject: [PATCH] Remove FishingHook reference on Craft Entity removal
-
-TODO 1.17 isn't this supposed to be applied to when the fish hook is removed _in general_? Not just in Bukkit api calls?
-No longer needed, fishing is set to null when FishingHook is removed.
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java
-index 6bfa984781a483d048ef4318761203c701d8a632..3c18712040da8d6ece24fd817b7846836b8353c1 100644
---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java
-+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftFishHook.java
-@@ -119,4 +119,14 @@ public class CraftFishHook extends CraftProjectile implements FishHook {
- public HookState getState() {
- return HookState.values()[this.getHandle().currentState.ordinal()];
- }
-+
-+ // Paper start
-+ @Override
-+ public void remove() {
-+ super.remove();
-+ if (getHandle().getPlayerOwner() != null) {
-+ getHandle().getPlayerOwner().fishing = null;
-+ }
-+ }
-+ // Paper end
- }
diff --git a/patches/removed/1.18.2/0109-Option-to-remove-corrupt-tile-entities.patch b/patches/removed/1.18.2/0109-Option-to-remove-corrupt-tile-entities.patch
deleted file mode 100644
index 6de0fe1b52..0000000000
--- a/patches/removed/1.18.2/0109-Option-to-remove-corrupt-tile-entities.patch
+++ /dev/null
@@ -1,47 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Zach Brown <[email protected]>
-Date: Wed, 5 Oct 2016 16:27:36 -0500
-Subject: [PATCH] Option to remove corrupt tile entities
-
-Corrupt tile entities are never added to the world in the first place anymore.
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index d7734fbc6b684b14bc32c94e65947fb41aae126a..80345730b8ccc11d3d0833485d25b03f614aeee2 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -347,4 +347,9 @@ public class PaperWorldConfig {
- preventTntFromMovingInWater = getBoolean("prevent-tnt-from-moving-in-water", false);
- log("Prevent TNT from moving in water: " + preventTntFromMovingInWater);
- }
-+
-+ public boolean removeCorruptTEs = false;
-+ private void removeCorruptTEs() {
-+ removeCorruptTEs = getBoolean("remove-corrupt-tile-entities", false);
-+ }
- }
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 3ecb007cf0ae781a2a68ca0a190616daf3e4aa88..e9436ab14bac2b5267239bcb27ad450d93c680e4 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -259,7 +259,7 @@ public class LevelChunk extends ChunkAccess {
- }
-
- this.setLightCorrect(protoChunk.isLightCorrect());
-- this.unsaved = true;
-+ this.setUnsaved(true);
- this.needsDecoration = true; // CraftBukkit
- // CraftBukkit start
- this.persistentDataContainer = protoChunk.persistentDataContainer; // SPIGOT-6814: copy PDC to account for 1.17 to 1.18 chunk upgrading.
-@@ -582,6 +582,12 @@ public class LevelChunk extends ChunkAccess {
- "Chunk coordinates: " + (this.chunkPos.x * 16) + "," + (this.chunkPos.z * 16));
- e.printStackTrace();
- ServerInternalException.reportInternalException(e);
-+
-+ if (this.level.paperConfig.removeCorruptTEs) {
-+ this.removeBlockEntity(blockEntity.getBlockPos());
-+ this.setUnsaved(true);
-+ org.bukkit.Bukkit.getLogger().info("Removing corrupt tile entity");
-+ }
- // Paper end
- // CraftBukkit end
- }
diff --git a/patches/removed/1.18.2/0115-Don-t-let-fishinghooks-use-portals.patch b/patches/removed/1.18.2/0115-Don-t-let-fishinghooks-use-portals.patch
deleted file mode 100644
index c2ccabc19a..0000000000
--- a/patches/removed/1.18.2/0115-Don-t-let-fishinghooks-use-portals.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Zach Brown <[email protected]>
-Date: Fri, 16 Dec 2016 16:03:19 -0600
-Subject: [PATCH] Don't let fishinghooks use portals
-
-Fishing hooks have canChangeDimensions to false, preventing them from being able to switch dimensions
-via portals.
-
-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 24192a91d9f5c890a316ec150d4aec84073cb61a..3b23279ce994b9684dbc10157839f5fc47edfabd 100644
---- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
-+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
-@@ -255,6 +255,11 @@ public class FishingHook extends Projectile {
-
- this.setDeltaMovement(this.getDeltaMovement().scale(0.92D));
- this.reapplyPosition();
-+ // Paper start - These shouldn't be going through portals
-+ if (this.isInsidePortal) {
-+ this.discard();
-+ }
-+ // Paper end
- }
- }
-
diff --git a/patches/removed/1.18.2/0840-Fix-riding-distance-statistics.patch b/patches/removed/1.18.2/0840-Fix-riding-distance-statistics.patch
deleted file mode 100644
index c5ac2d1f55..0000000000
--- a/patches/removed/1.18.2/0840-Fix-riding-distance-statistics.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Marvin Rieple <[email protected]>
-Date: Sun, 5 Dec 2021 16:42:07 +0100
-Subject: [PATCH] Fix riding distance statistics
-
-Fixes entity ride distance stats not being awarded correctly.
-Based upon https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/pull-requests/900
-
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index f73a94ac7009d41cd99b229e28d969586d6df419..0c87166beb89dd220e2f7aed1c47a331297eef0d 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -606,7 +606,14 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
- Location curPos = this.getCraftPlayer().getLocation(); // Spigot
-
- entity.absMoveTo(d3, d4, d5, f, f1);
-- this.player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
-+ // Paper start - SPIGOT-4396: Synchronize player and vehicle
-+ // Based upon https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/pull-requests/900
-+ Vec3 oldPlayerPosition = this.player.position();
-+ entity.positionRider(this.player);
-+ this.player.xo = oldPlayerPosition.x;
-+ this.player.yo = oldPlayerPosition.y;
-+ this.player.zo = oldPlayerPosition.z;
-+ // Paper end
- // Paper start - optimise out extra getCubes
- boolean teleportBack = flag1; // violating this is always a fail
- if (!teleportBack) {
-@@ -618,10 +625,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
- }
- if (teleportBack) { // Paper end - optimise out extra getCubes
- entity.absMoveTo(d0, d1, d2, f, f1);
-- this.player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
-+ // Paper start - SPIGOT-6475
-+ entity.positionRider(this.player);
-+ this.player.xo = oldPlayerPosition.x;
-+ this.player.yo = oldPlayerPosition.y;
-+ this.player.zo = oldPlayerPosition.z;
-+ // Paper end
- this.connection.send(new ClientboundMoveVehiclePacket(entity));
- return;
- }
-+ player.checkRidingStatistics(player.getX() - oldPlayerPosition.x, player.getY() - oldPlayerPosition.y, player.getZ() - oldPlayerPosition.z); // Paper - SPIGOT-6475: Update riding statistic
-
- // CraftBukkit start - fire PlayerMoveEvent
- Player player = this.getCraftPlayer();
diff --git a/patches/removed/1.18/0368-No-Tick-view-distance-implementation.patch b/patches/removed/1.18/0368-No-Tick-view-distance-implementation.patch
deleted file mode 100644
index ecb7448c18..0000000000
--- a/patches/removed/1.18/0368-No-Tick-view-distance-implementation.patch
+++ /dev/null
@@ -1,699 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Tue, 5 May 2020 21:23:34 -0700
-Subject: [PATCH] No-Tick view distance implementation
-
-Implements world view distance getters/setters
-
-Per-Player is absent due to difficulty of maintaining
-the diff required to make it happen.
-
-diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
-index ee53453440177537fc653ea156785d7591498614..cfe293881f68c8db337c3a48948362bb7b3e3522 100644
---- a/src/main/java/co/aikar/timings/TimingsExport.java
-+++ b/src/main/java/co/aikar/timings/TimingsExport.java
-@@ -152,7 +152,8 @@ public class TimingsExport extends Thread {
- pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> {
- return pair(rule, world.getWorld().getGameRuleValue(rule));
- })),
-- pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance())
-+ pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance()),
-+ pair("notick-viewdistance", world.getChunkSource().chunkMap.getEffectiveNoTickViewDistance())
- ));
- }));
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index 367bea78d479b73b35a324c58f8f9b981d9c8ccf..604a0b423ce7863ad872e111257ac2fe8d635d5a 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -531,6 +531,11 @@ public class PaperWorldConfig {
- lightQueueSize = getInt("light-queue-size", lightQueueSize);
- }
-
-+ public int noTickViewDistance;
-+ private void viewDistance() {
-+ this.noTickViewDistance = this.getInt("viewdistances.no-tick-view-distance", -1);
-+ }
-+
- public boolean antiXray;
- public EngineMode engineMode;
- public int maxBlockHeight;
-diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
-index 2fe519d4059fac06781c30e140895b604e13104f..1d469a9ea0049687d7686f88382ac14514ad3bee 100644
---- a/src/main/java/net/minecraft/server/MCUtil.java
-+++ b/src/main/java/net/minecraft/server/MCUtil.java
-@@ -641,7 +641,8 @@ public final class MCUtil {
- });
-
- worldData.addProperty("name", world.getWorld().getName());
-- worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
-+ worldData.addProperty("view-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance());
-+ worldData.addProperty("no-view-distance", world.getChunkSource().chunkMap.getRawNoTickViewDistance());
- worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
- worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange);
- worldData.addProperty("visible-chunk-count", visibleChunks.size());
-diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 47b3f4d84a9c7c730d07442ec69c064243d71ee1..84f370e887a3e7ff49296bdf8d6d8de9cc194cfb 100644
---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -75,6 +75,17 @@ public class ChunkHolder {
-
- boolean isUpdateQueued = false; // Paper
- private final ChunkMap chunkMap; // Paper
-+ // Paper start - no-tick view distance
-+ public final LevelChunk getSendingChunk() {
-+ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used
-+ // in Chunk's neighbour callback
-+ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z);
-+ if (ret != null && ret.areNeighboursLoaded(1)) {
-+ return ret;
-+ }
-+ return null;
-+ }
-+ // Paper end - no-tick view distance
-
- public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
- this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
-@@ -204,7 +215,7 @@ public class ChunkHolder {
- }
-
- public void blockChanged(BlockPos pos) {
-- LevelChunk chunk = this.getTickingChunk();
-+ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
-
- if (chunk != null) {
- int i = this.levelHeightAccessor.getSectionIndex(pos.getY());
-@@ -220,7 +231,7 @@ public class ChunkHolder {
- }
-
- public void sectionLightChanged(LightLayer lightType, int y) {
-- LevelChunk chunk = this.getTickingChunk();
-+ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
-
- if (chunk != null) {
- chunk.setUnsaved(true);
-@@ -310,9 +321,48 @@ public class ChunkHolder {
- }
-
- public void broadcast(Packet<?> packet, boolean onlyOnWatchDistanceEdge) {
-- this.playerProvider.getPlayers(this.pos, onlyOnWatchDistanceEdge).forEach((entityplayer) -> {
-- entityplayer.connection.send(packet);
-- });
-+ // Paper start - per player view distance
-+ // there can be potential desync with player's last mapped section and the view distance map, so use the
-+ // view distance map here.
-+ com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerViewDistanceBroadcastMap;
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players = viewDistanceMap.getObjectsInRange(this.pos);
-+ if (players == null) {
-+ return;
-+ }
-+
-+ if (onlyOnWatchDistanceEdge) { // flag -> border only
-+ Object[] backingSet = players.getBackingSet();
-+ for (int i = 0, len = backingSet.length; i < len; ++i) {
-+ Object temp = backingSet[i];
-+ if (!(temp instanceof ServerPlayer)) {
-+ continue;
-+ }
-+ ServerPlayer player = (ServerPlayer)temp;
-+
-+ int viewDistance = viewDistanceMap.getLastViewDistance(player);
-+ long lastPosition = viewDistanceMap.getLastCoordinate(player);
-+
-+ int distX = Math.abs(net.minecraft.server.MCUtil.getCoordinateX(lastPosition) - this.pos.x);
-+ int distZ = Math.abs(net.minecraft.server.MCUtil.getCoordinateZ(lastPosition) - this.pos.z);
-+
-+ if (Math.max(distX, distZ) == viewDistance) {
-+ player.connection.send(packet);
-+ }
-+ }
-+ } else {
-+ Object[] backingSet = players.getBackingSet();
-+ for (int i = 0, len = backingSet.length; i < len; ++i) {
-+ Object temp = backingSet[i];
-+ if (!(temp instanceof ServerPlayer)) {
-+ continue;
-+ }
-+ ServerPlayer player = (ServerPlayer)temp;
-+ player.connection.send(packet);
-+ }
-+ }
-+
-+ return;
-+ // Paper end - per player view distance
- }
-
- public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) {
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 5bbdf56179d2e5fd0b42c37c84c9d4bc5faaee24..f6ff29613d09b82185c2b2132d1ed34b0f71c222 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -169,21 +169,68 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- // Paper start - distance maps
- private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
-+ // Paper start - no-tick view distance
-+ int noTickViewDistance;
-+ public final int getRawNoTickViewDistance() {
-+ return this.noTickViewDistance;
-+ }
-+ public final int getEffectiveNoTickViewDistance() {
-+ return this.noTickViewDistance == -1 ? this.getEffectiveViewDistance() : this.noTickViewDistance;
-+ }
-+ public final int getLoadViewDistance() {
-+ return Math.max(this.getEffectiveViewDistance(), this.getEffectiveNoTickViewDistance());
-+ }
-+
-+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceBroadcastMap;
-+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceTickMap;
-+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceNoTickMap;
-+ // Paper end - no-tick view distance
-
- void addPlayerToDistanceMaps(ServerPlayer player) {
- int chunkX = MCUtil.getChunkCoordinate(player.getX());
- int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
- // Note: players need to be explicitly added to distance maps before they can be updated
-+ // Paper start - no-tick view distance
-+ int effectiveTickViewDistance = this.getEffectiveViewDistance();
-+ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance);
-+
-+ if (!this.skipPlayer(player)) {
-+ this.playerViewDistanceTickMap.add(player, chunkX, chunkZ, effectiveTickViewDistance);
-+ this.playerViewDistanceNoTickMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send)
-+ }
-+
-+ player.needsChunkCenterUpdate = true;
-+ this.playerViewDistanceBroadcastMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
-+ player.needsChunkCenterUpdate = false;
-+ // Paper end - no-tick view distance
- }
-
- void removePlayerFromDistanceMaps(ServerPlayer player) {
-
-+ // Paper start - no-tick view distance
-+ this.playerViewDistanceBroadcastMap.remove(player);
-+ this.playerViewDistanceTickMap.remove(player);
-+ this.playerViewDistanceNoTickMap.remove(player);
-+ // Paper end - no-tick view distance
- }
-
- void updateMaps(ServerPlayer player) {
- int chunkX = MCUtil.getChunkCoordinate(player.getX());
- int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
- // Note: players need to be explicitly added to distance maps before they can be updated
-+ // Paper start - no-tick view distance
-+ int effectiveTickViewDistance = this.getEffectiveViewDistance();
-+ int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance);
-+
-+ if (!this.skipPlayer(player)) {
-+ this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveTickViewDistance);
-+ this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send)
-+ }
-+
-+ player.needsChunkCenterUpdate = true;
-+ this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured
-+ player.needsChunkCenterUpdate = false;
-+ // Paper end - no-tick view distance
- }
- // Paper end
- // Paper start
-@@ -257,6 +304,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- this.dataRegionManager = new io.papermc.paper.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new);
- this.regionManagers.add(this.dataRegionManager);
- // Paper end
-+ // Paper start - no-tick view distance
-+ this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance);
-+ this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
-+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
-+ if (newState.size() != 1) {
-+ return;
-+ }
-+ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ);
-+ if (chunk == null || !chunk.areNeighboursLoaded(2)) {
-+ return;
-+ }
-+
-+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
-+ ChunkMap.this.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
-+ },
-+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
-+ if (newState != null) {
-+ return;
-+ }
-+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
-+ ChunkMap.this.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
-+ });
-+ this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
-+ this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
-+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
-+ if (player.needsChunkCenterUpdate) {
-+ player.needsChunkCenterUpdate = false;
-+ player.connection.send(new ClientboundSetChunkCacheCenterPacket(currPosX, currPosZ));
-+ }
-+ ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), new Packet[2], false, true); // unloaded, loaded
-+ },
-+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
-+ ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), null, true, false); // unloaded, loaded
-+ });
-+ // Paper end - no-tick view distance
- }
-
- private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
-@@ -954,14 +1040,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- completablefuture1.thenAcceptAsync((either) -> {
- either.ifLeft((chunk) -> {
- this.tickingGenerated.getAndIncrement();
-- Packet<?>[] apacket = new Packet[2];
--
-- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
-- this.playerLoadedChunk(entityplayer, apacket, chunk);
-- });
-+ // Paper - no-tick view distance - moved to Chunk neighbour update
- });
- }, (runnable) -> {
-- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
-+ this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable)); // Paper - diff on change, this is the scheduling method copied in Chunk used to schedule chunk broadcasts (on change it needs to be copied again)
- });
- return completablefuture1;
- }
-@@ -1054,27 +1136,34 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
-
- public void setViewDistance(int watchDistance) {
-- int j = Mth.clamp(watchDistance + 1, 3, 33);
-+ int j = Mth.clamp(watchDistance + 1, 3, 33); // Paper - diff on change, these make the lower view distance limit 2 and the upper 32
-
- if (j != this.viewDistance) {
- int k = this.viewDistance;
-
- this.viewDistance = j;
-- this.distanceManager.updatePlayerTickets(this.viewDistance);
-- ObjectIterator objectiterator = this.updatingChunkMap.values().iterator();
-+ this.setNoTickViewDistance(this.getRawNoTickViewDistance()); // Paper - no-tick view distance - propagate changes to no-tick, which does the actual chunk loading/sending
-+ }
-
-- while (objectiterator.hasNext()) {
-- ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
-- ChunkPos chunkcoordintpair = playerchunk.getPos();
-- Packet<?>[] apacket = new Packet[2];
-+ }
-
-- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
-- int l = ChunkMap.checkerboardDistance(chunkcoordintpair, entityplayer, true);
-- boolean flag = l <= k;
-- boolean flag1 = l <= this.viewDistance;
-+ // Paper start - no-tick view distance
-+ public final void setNoTickViewDistance(int viewDistance) {
-+ viewDistance = viewDistance == -1 ? -1 : Mth.clamp(viewDistance, 2, 32);
-
-- this.updateChunkTracking(entityplayer, chunkcoordintpair, apacket, flag, flag1);
-- });
-+ this.noTickViewDistance = viewDistance;
-+ int loadViewDistance = this.getLoadViewDistance();
-+ this.distanceManager.setNoTickViewDistance(loadViewDistance + 2 + 2); // add 2 to account for the change to 31 -> 33 tickets // see notes in the distance map updating for the other + 2
-+
-+ if (this.level != null && this.level.players != null) { // this can be called from constructor, where these aren't set
-+ for (ServerPlayer player : this.level.players) {
-+ net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection;
-+ if (connection != null) {
-+ // moved in from PlayerList
-+ connection.send(new net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket(loadViewDistance));
-+ }
-+ this.updateMaps(player);
-+ // Paper end - no-tick view distance
- }
- }
-
-@@ -1086,7 +1175,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong());
-
- if (playerchunk != null) {
-- LevelChunk chunk = playerchunk.getTickingChunk();
-+ LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - no-tick view distance
-
- if (chunk != null) {
- this.playerLoadedChunk(player, packets, chunk);
-@@ -1293,13 +1382,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- this.removePlayerFromDistanceMaps(player); // Paper - distance maps
- }
-
-- for (int k = i - this.viewDistance; k <= i + this.viewDistance; ++k) {
-- for (int l = j - this.viewDistance; l <= j + this.viewDistance; ++l) {
-- ChunkPos chunkcoordintpair = new ChunkPos(k, l);
--
-- this.updateChunkTracking(player, chunkcoordintpair, new Packet[2], !added, added);
-- }
-- }
-+ // Paper - broadcast view distance map handles this (see remove/add calls above)
-
- }
-
-@@ -1307,7 +1390,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- SectionPos sectionposition = SectionPos.of((Entity) player);
-
- player.setLastSectionPos(sectionposition);
-- player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z()));
-+ // player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z())); // Paper - distance map handles this now
- return sectionposition;
- }
-
-@@ -1362,6 +1445,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- int k1;
- int l1;
-
-+ /* // Paper start - replaced by distance map
- if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) {
- k1 = Math.min(i, i1) - this.viewDistance;
- l1 = Math.min(j, j1) - this.viewDistance;
-@@ -1400,6 +1484,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
- }
- }
-+ */ // Paper end - replaced by distance map
-
- this.updateMaps(player); // Paper - distance maps
-
-@@ -1407,11 +1492,46 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- @Override
- public Stream<ServerPlayer> getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) {
-- return this.playerMap.getPlayers(chunkPos.toLong()).filter((entityplayer) -> {
-- int i = ChunkMap.checkerboardDistance(chunkPos, entityplayer, true);
--
-- return i > this.viewDistance ? false : !onlyOnWatchDistanceEdge || i == this.viewDistance;
-- });
-+ // Paper start - per player view distance
-+ // there can be potential desync with player's last mapped section and the view distance map, so use the
-+ // view distance map here.
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> inRange = this.playerViewDistanceBroadcastMap.getObjectsInRange(chunkPos);
-+
-+ if (inRange == null) {
-+ return Stream.empty();
-+ }
-+ // all current cases are inlined so we wont hit this code, it's just in case plugins or future updates use it
-+ List<ServerPlayer> players = new java.util.ArrayList<>();
-+ Object[] backingSet = inRange.getBackingSet();
-+
-+ if (onlyOnWatchDistanceEdge) { // flag -> border only
-+ for (int i = 0, len = backingSet.length; i < len; ++i) {
-+ Object temp = backingSet[i];
-+ if (!(temp instanceof ServerPlayer)) {
-+ continue;
-+ }
-+ ServerPlayer player = (ServerPlayer)temp;
-+ int viewDistance = this.playerViewDistanceBroadcastMap.getLastViewDistance(player);
-+ long lastPosition = this.playerViewDistanceBroadcastMap.getLastCoordinate(player);
-+
-+ int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - chunkPos.x);
-+ int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - chunkPos.z);
-+ if (Math.max(distX, distZ) == viewDistance) {
-+ players.add(player);
-+ }
-+ }
-+ } else {
-+ for (int i = 0, len = backingSet.length; i < len; ++i) {
-+ Object temp = backingSet[i];
-+ if (!(temp instanceof ServerPlayer)) {
-+ continue;
-+ }
-+ ServerPlayer player = (ServerPlayer)temp;
-+ players.add(player);
-+ }
-+ }
-+ return players.stream();
-+ // Paper end - per player view distance
- }
-
- public void addEntity(Entity entity) {
-@@ -1532,6 +1652,47 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- }
-
-+ // Paper start
-+ private static int getLightMask(final LevelChunk chunk) {
-+ final net.minecraft.world.level.chunk.LevelChunkSection[] chunkSections = chunk.getSections();
-+ int mask = 0;
-+
-+ for (int i = 0; i < chunkSections.length; ++i) {
-+ /*
-+
-+
-+Lightmasks have 18 bits, from the -1 (void) section until the 17th (air) section.
-+Sections go from 0..16. Now whenever a section is not empty, it can potentially change lighting for the section itself, the section below and the section above, hence the bitmask 111b, which is 7d.
-+
-+ */
-+ mask |= (net.minecraft.world.level.chunk.LevelChunkSection.isEmpty(chunkSections[i]) ? 0 : 7) << i;
-+ }
-+
-+ return mask;
-+ }
-+
-+ private static int getCeilingLightMask(final LevelChunk chunk) {
-+ int mask = getLightMask(chunk);
-+
-+ /*
-+ It is similar to get highest bit, it would turn an 001010 into an 001111 so basically the highest bit and all below.
-+ We then invert this, so we'd have 110000 and compare that to the "main" chunk.
-+ This is because the bug only appears when the current chunks lightmaps are higher than those of the neighbors, thus we can omit sending neighbors which are lower than the current chunks lights.
-+
-+ so TLDR is that getCeilingLightMask returns a light mask with all bits set below the highest affected section. We could also count the number of leading zeros and invert them, somehow.
-+ @TODO: Implement Leafs suggestion
-+ either use Integer#numberOfLeadingZeros or document what this bithack is supposed to be doing then
-+ */
-+ mask |= mask >> 1;
-+ mask |= mask >> 2;
-+ mask |= mask >> 4;
-+ mask |= mask >> 8;
-+ mask |= mask >> 16;
-+
-+ return mask;
-+ }
-+ // Paper end
-+
- public void playerLoadedChunk(ServerPlayer player, Packet<?>[] packets, LevelChunk chunk) {
- if (packets[0] == null) {
- packets[0] = new ClientboundLevelChunkPacket(chunk, chunk.level.chunkPacketBlockController.shouldModify(player, chunk)); // Paper - Ani-Xray - Bypass
-diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index 45c7ebe67019cdbe88b6617a95d5c40d3a68286c..38eebda226e007c8910e04f502ce218cdfe1d456 100644
---- a/src/main/java/net/minecraft/server/level/DistanceManager.java
-+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -275,8 +275,8 @@ public abstract class DistanceManager {
- return s;
- }
-
-- protected void updatePlayerTickets(int viewDistance) {
-- this.playerTicketManager.updateViewDistance(viewDistance);
-+ protected void setNoTickViewDistance(int i) { // Paper - force abi breakage on usage change
-+ this.playerTicketManager.updateViewDistance(i);
- }
-
- public int getNaturalSpawnChunkCount() {
-@@ -503,7 +503,7 @@ public abstract class DistanceManager {
-
- private void onLevelChange(long pos, int distance, boolean oldWithinViewDistance, boolean withinViewDistance) {
- if (oldWithinViewDistance != withinViewDistance) {
-- Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, DistanceManager.PLAYER_TICKET_LEVEL, new ChunkPos(pos));
-+ Ticket<?> ticket = new Ticket<>(TicketType.PLAYER, 33, new ChunkPos(pos)); // Paper - no-tick view distance
-
- if (withinViewDistance) {
- DistanceManager.this.ticketThrottlerInput.tell(ChunkTaskPriorityQueueSorter.message(() -> {
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index e8cfc0856c33dab2d94bfebd0cc70823a2a4b69c..97d6963b6c7f0fb71324ac760df940fbf03e321f 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -242,6 +242,7 @@ public class ServerPlayer extends Player {
- public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper
-
- public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleHashSet; // Paper
-+ boolean needsChunkCenterUpdate; // Paper - no-tick view distance
-
- public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) {
- super(world, world.getSharedSpawnPos(), world.getSharedSpawnAngle(), profile);
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index a2eb7689eafe20db59357ab3fad0e59cdef3481a..c0e8e863708ac794b7271765cdae99dc4df14caa 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -244,7 +244,7 @@ public abstract class PlayerList {
- boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
-
- // Spigot - view distance
-- playerconnection.send(new ClientboundLoginPacket(player.getId(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), worlddata.isHardcore(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat()));
-+ playerconnection.send(new ClientboundLoginPacket(player.getId(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), worlddata.isHardcore(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.getLoadViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat())); // Paper - no-tick view distance
- player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
- playerconnection.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName())));
- playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
-@@ -798,7 +798,7 @@ public abstract class PlayerList {
- // CraftBukkit start
- LevelData worlddata = worldserver1.getLevelData();
- entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionType(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), flag));
-- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot
-+ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.getLoadViewDistance())); // Spigot // Paper - no-tick view distance
- entityplayer1.setLevel(worldserver1);
- entityplayer1.unsetRemoved();
- entityplayer1.connection.teleport(new Location(worldserver1.getWorld(), entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot()));
-@@ -1283,7 +1283,7 @@ public abstract class PlayerList {
-
- public void setViewDistance(int viewDistance) {
- this.viewDistance = viewDistance;
-- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance));
-+ //this.sendAll(new PacketPlayOutViewDistance(i)); // Paper - move into setViewDistance
- Iterator iterator = this.server.getAllLevels().iterator();
-
- while (iterator.hasNext()) {
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 35209090439d5ab3bf5c37de28a39e60d482b64c..f196a184c05d5f87faee78323343d1fe19287c07 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -519,8 +519,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- this.setBlocksDirty(blockposition, iblockdata1, iblockdata2);
- }
-
-- if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
-+ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement // Paper - diff on change, see below
- this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
-+ // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance
-+ // if copied from above
-+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((ServerLevel)this).getChunkSource().chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) {
-+ ((ServerLevel)this).getChunkSource().blockChanged(blockposition);
-+ // Paper end - per player view distance
- }
-
- if ((i & 1) != 0) {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 515e28eea8cbab261320352ee0db9b877807f3ed..83ed84f89a036d3768b22a36bc8a0bfc2bc29ec7 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -33,7 +33,10 @@ import net.minecraft.core.Registry;
- import net.minecraft.core.SectionPos;
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.network.FriendlyByteBuf;
-+import net.minecraft.network.protocol.Packet;
- import net.minecraft.server.level.ChunkHolder;
-+import net.minecraft.server.level.ChunkMap;
-+import net.minecraft.server.level.ChunkTaskPriorityQueueSorter;
- import net.minecraft.server.level.ServerChunkCache;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.util.profiling.ProfilerFiller;
-@@ -227,7 +230,51 @@ public class LevelChunk implements ChunkAccess {
- }
-
- protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) {
-+ // Paper start - no-tick view distance
-+ ServerChunkCache chunkProviderServer = ((ServerLevel)this.level).getChunkSource();
-+ ChunkMap chunkMap = chunkProviderServer.chunkMap;
-+ // this code handles the addition of ticking tickets - the distance map handles the removal
-+ if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) {
-+ if (chunkMap.playerViewDistanceTickMap.getObjectsInRange(this.coordinateKey) != null) {
-+ // now we're ready for entity ticking
-+ chunkProviderServer.mainThreadProcessor.execute(() -> {
-+ // double check that this condition still holds.
-+ if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerViewDistanceTickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) {
-+ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.PLAYER, LevelChunk.this.chunkPos, 31, LevelChunk.this.chunkPos); // 31 -> entity ticking, TODO check on update
-+ }
-+ });
-+ }
-+ }
-
-+ // this code handles the chunk sending
-+ if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) {
-+ if (chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(this.coordinateKey) != null) {
-+ // now we're ready to send
-+ chunkMap.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(chunkMap.getUpdatingChunkIfPresent(this.coordinateKey), (() -> { // Copied frm PlayerChunkMap
-+ // double check that this condition still holds.
-+ if (!LevelChunk.this.areNeighboursLoaded(1)) {
-+ return;
-+ }
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<net.minecraft.server.level.ServerPlayer> inRange = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(LevelChunk.this.coordinateKey);
-+ if (inRange == null) {
-+ return;
-+ }
-+
-+ // broadcast
-+ Object[] backingSet = inRange.getBackingSet();
-+ Packet[] chunkPackets = new Packet[2];
-+ for (int index = 0, len = backingSet.length; index < len; ++index) {
-+ Object temp = backingSet[index];
-+ if (!(temp instanceof net.minecraft.server.level.ServerPlayer)) {
-+ continue;
-+ }
-+ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)temp;
-+ chunkMap.playerLoadedChunk(player, chunkPackets, LevelChunk.this);
-+ }
-+ })));
-+ }
-+ }
-+ // Paper end - no-tick view distance
- }
-
- public final boolean isAnyNeighborsLoaded() {
-@@ -1011,7 +1058,7 @@ public class LevelChunk implements ChunkAccess {
- BlockState iblockdata = this.getBlockState(blockposition);
- BlockState iblockdata1 = Block.updateFromNeighbourShapes(iblockdata, (LevelAccessor) this.level, blockposition);
-
-- this.level.setBlock(blockposition, iblockdata1, 20);
-+ this.level.setBlock(blockposition, iblockdata1, 20 | 2); // Paper - We send chunks before they're ticking ready, so we need to notify here
- }
-
- this.postProcessing[i].clear();
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index d580a66dd741b63dd8ed89d7b976b1612cfb4d90..01ac6e7e7b4b6c61d01684c77ecc2238afcaa8f1 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -1922,10 +1922,39 @@ public class CraftWorld extends CraftRegionAccessor implements World {
- // Spigot start
- @Override
- public int getViewDistance() {
-- return world.spigotConfig.viewDistance;
-+ return getHandle().getChunkSource().chunkMap.getEffectiveViewDistance(); // Paper - no-tick view distance
- }
- // Spigot end
-
-+ // Paper start - per player view distance
-+ @Override
-+ public void setViewDistance(int viewDistance) {
-+ if (viewDistance < 2 || viewDistance > 32) {
-+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
-+ }
-+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
-+ if (viewDistance != chunkMap.getEffectiveViewDistance()) {
-+ chunkMap.setViewDistance(viewDistance);
-+ }
-+ }
-+
-+ @Override
-+ public int getNoTickViewDistance() {
-+ return getHandle().getChunkSource().chunkMap.getEffectiveNoTickViewDistance();
-+ }
-+
-+ @Override
-+ public void setNoTickViewDistance(int viewDistance) {
-+ if ((viewDistance < 2 || viewDistance > 32) && viewDistance != -1) {
-+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
-+ }
-+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
-+ if (viewDistance != chunkMap.getRawNoTickViewDistance()) {
-+ chunkMap.setNoTickViewDistance(viewDistance);
-+ }
-+ }
-+ // Paper end - per player view distance
-+
- // Spigot start
- private final org.bukkit.World.Spigot spigot = new org.bukkit.World.Spigot()
- {
-diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
-index e0302f82356e8cba848aa8cec1e821e02abbd6f6..85449fc6d19974622588e7f13e1dc78c8dffbeee 100644
---- a/src/main/java/org/spigotmc/ActivationRange.java
-+++ b/src/main/java/org/spigotmc/ActivationRange.java
-@@ -188,7 +188,7 @@ public class ActivationRange
- maxRange = Math.max( maxRange, waterActivationRange );
- maxRange = Math.max( maxRange, villagerActivationRange );
- // Paper end
-- maxRange = Math.min( ( world.spigotConfig.viewDistance << 4 ) - 8, maxRange );
-+ maxRange = Math.min( ( ((net.minecraft.server.level.ServerLevel)world).getChunkSource().chunkMap.getEffectiveViewDistance() << 4 ) - 8, maxRange ); // Paper - no-tick view distance
-
- for ( Player player : world.players() )
- {
diff --git a/patches/removed/1.18/0388-Optimise-TickListServer-by-rewriting-it.patch b/patches/removed/1.18/0388-Optimise-TickListServer-by-rewriting-it.patch
deleted file mode 100644
index 5796e956f0..0000000000
--- a/patches/removed/1.18/0388-Optimise-TickListServer-by-rewriting-it.patch
+++ /dev/null
@@ -1,1127 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Fri, 14 Feb 2020 01:24:39 -0800
-Subject: [PATCH] Optimise TickListServer by rewriting it
-
-In my profiling TickListServer showed up as
-~10% for saving chunks and ~5% for the scheduling
-of items on a server with ~90 players at
-view distance = 5. Most of the performance
-loss is unneccessary.
-
-TickListServer has numerous performance issues:
- 1. Handling scheduled items is O(nlogn)
- 2. Getting scheduled items for a chunk is O(n),
- with n being the the number of scheduled items
- for all chunks (hits saving very hard)
- 3. Checking if an item is scheduled for the current tick is O(n),
- with n being the number of items scheduled for current tick
- 4. Items not in ticking chunks are churned in the scheduler
-
-The biggest issues are 4 & 2.
-
-We solve 1 by splitting up scheduled items into short and long scheduled,
-where we expect the vast majority of our entries to be in the short scheduled
-set. Handling short scheduled items is O(n) due to how the comparison
-process is reduced to mapping. See TickListServerInterval. However,
-this isn't memory-efficient - which is why long scheduled exists.
-Long scheduled is handled the same as TickListServer.
-
-2 is solved by mapping what entries are in what chunks.
-
-3 is solved by mapping what blocks have what scheduled for them.
-
-4 is solved by moving the items that are not in ticking chunks
-into a map of entries for that chunk. Once the chunk is moved
-to ticking, the items are re-scheduled.
-
-This patch has also added two flags to debug excessive tick delays:
--Dpaper.ticklist-warn-on-excessive-delay=true (false by default)
-and -Dpaper.ticklist-excessive-delay-threshold=ticks which
-sets the excessive tick delay to the specified ticks (defaults to
-60 * 20 ticks, aka 60 seconds)
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-index 26c5ae72f63d930bf6de2ec18a964ddfeca16379..e9954fa75412a7077950e3813af4b201c084f68f 100644
---- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-@@ -375,6 +375,13 @@ public class PaperConfig {
- maxBookTotalSizeMultiplier = getDouble("settings.book-size.total-multiplier", maxBookTotalSizeMultiplier);
- }
-
-+ public static boolean useOptimizedTickList = true;
-+ private static void useOptimizedTickList() {
-+ if (config.contains("settings.use-optimized-ticklist")) { // don't add default, hopefully temporary config
-+ useOptimizedTickList = config.getBoolean("settings.use-optimized-ticklist");
-+ }
-+ }
-+
- public static boolean asyncChunks = false;
- private static void asyncChunks() {
- ConfigurationSection section;
-diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..5fdaefc128956581be4bb9b34199fd6410563991
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java
-@@ -0,0 +1,628 @@
-+package com.destroystokyo.paper.server.ticklist;
-+
-+import java.util.function.Function;
-+import net.minecraft.CrashReport;
-+import net.minecraft.CrashReportCategory;
-+import net.minecraft.ReportedException;
-+import net.minecraft.core.BlockPos;
-+import net.minecraft.nbt.ListTag;
-+import net.minecraft.resources.ResourceLocation;
-+import net.minecraft.server.MCUtil;
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ServerChunkCache;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.world.level.ChunkPos;
-+import net.minecraft.world.level.ServerTickList;
-+import net.minecraft.world.level.TickNextTickData;
-+import net.minecraft.world.level.TickPriority;
-+import net.minecraft.world.level.block.state.BlockState;
-+import net.minecraft.world.level.levelgen.structure.BoundingBox;
-+import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
-+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
-+import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
-+import java.util.ArrayDeque;
-+import java.util.ArrayList;
-+import java.util.Collections;
-+import java.util.Comparator;
-+import java.util.Iterator;
-+import java.util.List;
-+import java.util.function.Consumer;
-+import java.util.function.Predicate;
-+
-+public final class PaperTickList<T> extends ServerTickList<T> { // extend to avoid breaking ABI
-+
-+ // in the order the state is expected to change (mostly)
-+ public static final int STATE_UNSCHEDULED = 1 << 0;
-+ public static final int STATE_SCHEDULED = 1 << 1; // scheduled for some tick
-+ public static final int STATE_PENDING_TICK = 1 << 2; // for this tick
-+ public static final int STATE_TICKING = 1 << 3;
-+ public static final int STATE_TICKED = 1 << 4; // after this, it gets thrown back to unscheduled
-+ public static final int STATE_CANCELLED_TICK = 1 << 5; // still gets moved to unscheduled after tick
-+
-+ private static final int SHORT_SCHEDULE_TICK_THRESHOLD = 20 * 20 + 1; // 20 seconds
-+
-+ private final ServerLevel world;
-+ private final Predicate<T> excludeFromScheduling;
-+ private final Function<T, ResourceLocation> getMinecraftKeyFrom;
-+ //private final Function<MinecraftKey, T> getObjectFronMinecraftKey;
-+ private final Consumer<TickNextTickData<T>> tickFunction;
-+
-+ private final co.aikar.timings.Timing timingCleanup; // Paper
-+ private final co.aikar.timings.Timing timingTicking; // Paper
-+ private final co.aikar.timings.Timing timingFinished;
-+
-+ // note: remove ops / add ops suck on fastutil, a chained hashtable implementation would work better, but Long...
-+ // try to alleviate with a very small load factor
-+ private final Long2ObjectOpenHashMap<ArrayList<TickNextTickData<T>>> entriesByBlock = new Long2ObjectOpenHashMap<>(1024, 0.25f);
-+ private final Long2ObjectOpenHashMap<ObjectRBTreeSet<TickNextTickData<T>>> entriesByChunk = new Long2ObjectOpenHashMap<>(1024, 0.25f);
-+ private final Long2ObjectOpenHashMap<ArrayList<TickNextTickData<T>>> pendingChunkTickLoad = new Long2ObjectOpenHashMap<>(1024, 0.5f);
-+
-+ // fastutil has O(1) first/last while TreeMap/TreeSet are log(n)
-+ private final ObjectRBTreeSet<TickNextTickData<T>> longScheduled = new ObjectRBTreeSet<>(TickListServerInterval.ENTRY_COMPARATOR);
-+
-+ private final ArrayDeque<TickNextTickData<T>> toTickThisTick = new ArrayDeque<>();
-+
-+ private final TickListServerInterval<T>[] shortScheduled = new TickListServerInterval[SHORT_SCHEDULE_TICK_THRESHOLD];
-+ {
-+ for (int i = 0, len = this.shortScheduled.length; i < len; ++i) {
-+ this.shortScheduled[i] = new TickListServerInterval<>();
-+ }
-+ }
-+ private int shortScheduledIndex;
-+
-+ private long currentTick;
-+
-+ private static final boolean WARN_ON_EXCESSIVE_DELAY = Boolean.getBoolean("paper.ticklist-warn-on-excessive-delay");
-+ private static final long EXCESSIVE_DELAY_THRESHOLD = Long.getLong("paper.ticklist-excessive-delay-threshold", 60 * 20).longValue(); // 1 min dfl
-+
-+ // assume index < length
-+ private static int getWrappedIndex(final int start, final int length, final int index) {
-+ final int next = start + index;
-+ return next < length ? next : next - length;
-+ }
-+
-+ private static int getNextIndex(final int curr, final int length) {
-+ final int next = curr + 1;
-+ return next < length ? next : 0;
-+ }
-+
-+ public PaperTickList(final ServerLevel world, final Predicate<T> excludeFromScheduling, final Function<T, ResourceLocation> getMinecraftKeyFrom,
-+ final Consumer<TickNextTickData<T>> tickFunction, final String timingsType) {
-+ super(world, excludeFromScheduling, getMinecraftKeyFrom, tickFunction, timingsType);
-+ this.world = world;
-+ this.excludeFromScheduling = excludeFromScheduling;
-+ this.getMinecraftKeyFrom = getMinecraftKeyFrom;
-+ this.tickFunction = tickFunction;
-+ this.timingCleanup = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Cleanup"); // Paper
-+ this.timingTicking = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Ticking"); // Paper
-+ this.timingFinished = co.aikar.timings.WorldTimingsHandler.getTickList(world, timingsType + " - Finish");
-+ this.currentTick = this.world.getGameTime();
-+ }
-+
-+ private void queueEntryForTick(final TickNextTickData<T> entry, final ServerChunkCache chunkProvider) {
-+ if (entry.tickState == STATE_SCHEDULED) {
-+ if (chunkProvider.isPositionTickingWithEntitiesLoaded(entry.pos)) {
-+ this.toTickThisTick.add(entry);
-+ entry.tickState = STATE_PENDING_TICK;
-+ } else {
-+ // we dump them to a map to avoid constantly re-scheduling them
-+ this.addToNotTickingReady(entry);
-+ }
-+ }
-+ }
-+
-+ private void addToNotTickingReady(final TickNextTickData<T> entry) {
-+ this.pendingChunkTickLoad.computeIfAbsent(MCUtil.getCoordinateKey(entry.pos), (long keyInMap) -> {
-+ return new ArrayList<>();
-+ }).add(entry);
-+ }
-+
-+ private void addToSchedule(final TickNextTickData<T> entry) {
-+ long delay = entry.triggerTick - (this.currentTick + 1);
-+ if (delay < SHORT_SCHEDULE_TICK_THRESHOLD) {
-+ if (delay < 0) {
-+ // longScheduled orders by tick time, short scheduled does not
-+ this.longScheduled.add(entry);
-+ } else {
-+ this.shortScheduled[getWrappedIndex(this.shortScheduledIndex, SHORT_SCHEDULE_TICK_THRESHOLD, (int)delay)].addEntryLast(entry);
-+ }
-+ } else {
-+ this.longScheduled.add(entry);
-+ }
-+ }
-+
-+ private void removeEntry(final TickNextTickData<T> entry) {
-+ entry.tickState = STATE_CANCELLED_TICK;
-+ // short/long scheduled will skip the entry
-+
-+ final BlockPos pos = entry.pos;
-+ final long blockKey = MCUtil.getBlockKey(pos);
-+
-+ final ArrayList<TickNextTickData<T>> currentEntries = this.entriesByBlock.get(blockKey);
-+
-+ if (currentEntries.size() == 1) {
-+ // it should contain our entry
-+ this.entriesByBlock.remove(blockKey);
-+ } else {
-+ // it's more likely that this entry is at the start of the list than the end
-+ for (int i = 0, len = currentEntries.size(); i < len; ++i) {
-+ final TickNextTickData<T> currentEntry = currentEntries.get(i);
-+ if (currentEntry == entry) {
-+ currentEntries.remove(i);
-+ break;
-+ }
-+ }
-+ }
-+
-+ final long chunkKey = MCUtil.getCoordinateKey(entry.pos);
-+
-+ ObjectRBTreeSet<TickNextTickData<T>> set = this.entriesByChunk.get(chunkKey);
-+
-+ set.remove(entry);
-+
-+ if (set.isEmpty()) {
-+ this.entriesByChunk.remove(chunkKey);
-+ }
-+
-+ ArrayList<TickNextTickData<T>> pendingTickingLoad = this.pendingChunkTickLoad.get(chunkKey);
-+
-+ if (pendingTickingLoad != null) {
-+ for (int i = 0, len = pendingTickingLoad.size(); i < len; ++i) {
-+ if (pendingTickingLoad.get(i) == entry) {
-+ pendingTickingLoad.remove(i);
-+ break;
-+ }
-+ }
-+
-+ if (pendingTickingLoad.isEmpty()) {
-+ this.pendingChunkTickLoad.remove(chunkKey);
-+ }
-+ }
-+
-+ long delay = entry.triggerTick - (this.currentTick + 1);
-+ if (delay >= SHORT_SCHEDULE_TICK_THRESHOLD) {
-+ this.longScheduled.remove(entry);
-+ }
-+ }
-+
-+ public void onChunkSetTicking(final int chunkX, final int chunkZ) {
-+ final ArrayList<TickNextTickData<T>> pending = this.pendingChunkTickLoad.remove(MCUtil.getCoordinateKey(chunkX, chunkZ));
-+ if (pending == null) {
-+ return;
-+ }
-+
-+ for (int i = 0, size = pending.size(); i < size; ++i) {
-+ final TickNextTickData<T> entry = pending.get(i);
-+ // already in all the relevant reference maps, just need to add to longScheduled or shortScheduled
-+ this.addToSchedule(entry);
-+ }
-+ }
-+
-+ private void prepare() {
-+ final long currentTick = this.currentTick;
-+
-+ final ServerChunkCache chunkProvider = this.world.getChunkSource();
-+
-+ // here we setup what's going to tick
-+
-+ // we don't remove items from shortScheduled (but do from longScheduled) because they're cleared at the end of
-+ // this tick
-+ if (this.longScheduled.isEmpty() || this.longScheduled.first().triggerTick > currentTick) {
-+ // nothing in longScheduled to worry about
-+ final TickListServerInterval<T> interval = this.shortScheduled[this.shortScheduledIndex];
-+ for (int i = 0, len = interval.byPriority.length; i < len; ++i) {
-+ for (final Iterator<TickNextTickData<T>> iterator = interval.byPriority[i].iterator(); iterator.hasNext();) {
-+ this.queueEntryForTick(iterator.next(), chunkProvider);
-+ }
-+ }
-+ } else {
-+ final TickListServerInterval<T> interval = this.shortScheduled[this.shortScheduledIndex];
-+
-+ // combine interval and longScheduled, keeping order
-+ final Comparator<TickNextTickData<T>> comparator = (Comparator)TickListServerInterval.ENTRY_COMPARATOR;
-+ final Iterator<TickNextTickData<T>> longScheduledIterator = this.longScheduled.iterator();
-+ TickNextTickData<T> longCurrent = longScheduledIterator.next();
-+
-+ for (int i = 0, len = interval.byPriority.length; i < len; ++i) {
-+ for (final Iterator<TickNextTickData<T>> iterator = interval.byPriority[i].iterator(); iterator.hasNext();) {
-+ final TickNextTickData<T> shortCurrent = iterator.next();
-+ if (longCurrent != null) {
-+ // drain longCurrent until we can add shortCurrent
-+ while (comparator.compare(longCurrent, shortCurrent) <= 0) {
-+ this.queueEntryForTick(longCurrent, chunkProvider);
-+ longScheduledIterator.remove();
-+ if (longScheduledIterator.hasNext()) {
-+ longCurrent = longScheduledIterator.next();
-+ if (longCurrent.triggerTick > currentTick) {
-+ longCurrent = null;
-+ break;
-+ }
-+ } else {
-+ longCurrent = null;
-+ break;
-+ }
-+ }
-+ }
-+ this.queueEntryForTick(shortCurrent, chunkProvider);
-+ }
-+ }
-+
-+ // add remaining from long scheduled
-+ for (;;) {
-+ if (longCurrent == null || longCurrent.triggerTick > currentTick) {
-+ break;
-+ }
-+ longScheduledIterator.remove();
-+ this.queueEntryForTick(longCurrent, chunkProvider);
-+
-+ if (longScheduledIterator.hasNext()) {
-+ longCurrent = longScheduledIterator.next();
-+ } else {
-+ break;
-+ }
-+ }
-+ }
-+ }
-+
-+ private boolean warnedAboutDesync;
-+
-+ @Override
-+ public void nextTick() {
-+ ++this.currentTick;
-+ if (this.currentTick != this.world.getGameTime()) {
-+ if (!this.warnedAboutDesync) {
-+ this.warnedAboutDesync = true;
-+ MinecraftServer.LOGGER.error("World tick desync detected! Expected " + this.currentTick + " ticks, but got " + this.world.getGameTime() + " ticks for world '" + this.world.getWorld().getName() + "'", new Throwable());
-+ MinecraftServer.LOGGER.error("Preventing redstone from breaking by refusing to accept new tick time");
-+ }
-+ }
-+ }
-+
-+ @Override
-+ public void tick() {
-+ final ServerChunkCache chunkProvider = this.world.getChunkSource();
-+
-+ this.world.getProfiler().push("cleaning");
-+ this.timingCleanup.startTiming();
-+
-+ this.prepare();
-+
-+ // this must be done here in case something schedules in the tick code
-+ this.shortScheduled[this.shortScheduledIndex].clear();
-+ this.shortScheduledIndex = getNextIndex(this.shortScheduledIndex, SHORT_SCHEDULE_TICK_THRESHOLD);
-+
-+ this.timingCleanup.stopTiming();
-+ this.world.getProfiler().popPush("ticking");
-+ this.timingTicking.startTiming();
-+
-+ for (final TickNextTickData<T> toTick : this.toTickThisTick) {
-+ if (toTick.tickState != STATE_PENDING_TICK) {
-+ // onTickEnd gets called at end of tick
-+ continue;
-+ }
-+ try {
-+ if (chunkProvider.isPositionTickingWithEntitiesLoaded(toTick.pos)) {
-+ toTick.tickState = STATE_TICKING;
-+ this.tickFunction.accept(toTick);
-+ if (toTick.tickState == STATE_TICKING) {
-+ toTick.tickState = STATE_TICKED;
-+ } // else it's STATE_CANCELLED_TICK
-+ } else {
-+ // re-schedule eventually
-+ toTick.tickState = STATE_SCHEDULED;
-+ this.addToNotTickingReady(toTick);
-+ }
-+ } catch (final Throwable thr) {
-+ // start copy from TickListServer // TODO check on update
-+ CrashReport crashreport = CrashReport.forThrowable(thr, "Exception while ticking");
-+ CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Block being ticked");
-+
-+ CrashReportCategory.populateBlockDetails(crashreportsystemdetails, this.world, toTick.pos, (BlockState) null);
-+ throw new ReportedException(crashreport);
-+ // end copy from TickListServer
-+ }
-+ }
-+
-+ this.timingTicking.stopTiming();
-+ this.world.getProfiler().pop();
-+ this.timingFinished.startTiming();
-+
-+ // finished ticking, actual cleanup time
-+ for (int i = 0, len = this.toTickThisTick.size(); i < len; ++i) {
-+ final TickNextTickData<T> entry = this.toTickThisTick.poll();
-+ if (entry.tickState != STATE_SCHEDULED) {
-+ // some entries get re-scheduled due to their chunk not being loaded/at correct status, so do not
-+ // call onTickEnd for them
-+ this.onTickEnd(entry);
-+ }
-+ }
-+
-+ this.timingFinished.stopTiming();
-+ }
-+
-+ private void onTickEnd(final TickNextTickData<T> entry) {
-+ if (entry.tickState == STATE_CANCELLED_TICK) {
-+ return;
-+ }
-+ entry.tickState = STATE_UNSCHEDULED;
-+
-+ final BlockPos pos = entry.pos;
-+ final long blockKey = MCUtil.getBlockKey(pos);
-+
-+ final ArrayList<TickNextTickData<T>> currentEntries = this.entriesByBlock.get(blockKey);
-+
-+ if (currentEntries.size() == 1) {
-+ // it should contain our entry
-+ this.entriesByBlock.remove(blockKey);
-+ } else {
-+ // it's more likely that this entry is at the start of the list than the end
-+ for (int i = 0, len = currentEntries.size(); i < len; ++i) {
-+ final TickNextTickData<T> currentEntry = currentEntries.get(i);
-+ if (currentEntry == entry) {
-+ currentEntries.remove(i);
-+ break;
-+ }
-+ }
-+ }
-+
-+ final long chunkKey = MCUtil.getCoordinateKey(entry.pos);
-+
-+ ObjectRBTreeSet<TickNextTickData<T>> set = this.entriesByChunk.get(chunkKey);
-+
-+ set.remove(entry);
-+
-+ if (set.isEmpty()) {
-+ this.entriesByChunk.remove(chunkKey);
-+ }
-+
-+ // already removed from longScheduled or shortScheduled
-+ }
-+
-+ @Override
-+ public boolean willTickThisTick(final BlockPos blockposition, final T data) {
-+ final ArrayList<TickNextTickData<T>> entries = this.entriesByBlock.get(MCUtil.getBlockKey(blockposition));
-+
-+ if (entries == null) {
-+ return false;
-+ }
-+
-+ for (int i = 0, size = entries.size(); i < size; ++i) {
-+ final TickNextTickData<T> entry = entries.get(i);
-+ if (entry.getType() == data && entry.tickState == STATE_PENDING_TICK) {
-+ return true;
-+ }
-+ }
-+
-+ return false;
-+ }
-+
-+ @Override
-+ public boolean hasScheduledTick(final BlockPos blockposition, final T data) {
-+ final ArrayList<TickNextTickData<T>> entries = this.entriesByBlock.get(MCUtil.getBlockKey(blockposition));
-+
-+ if (entries == null) {
-+ return false;
-+ }
-+
-+ for (int i = 0, size = entries.size(); i < size; ++i) {
-+ final TickNextTickData<T> entry = entries.get(i);
-+ if (entry.getType() == data && entry.tickState == STATE_SCHEDULED) {
-+ return true;
-+ }
-+ }
-+
-+ return false;
-+ }
-+
-+ @Override
-+ public void scheduleTick(BlockPos blockPosition, T t, int i, TickPriority tickListPriority) {
-+ this.schedule(blockPosition, t, i + this.currentTick, tickListPriority);
-+ }
-+
-+ public void schedule(final TickNextTickData<T> entry) {
-+ this.schedule(entry.pos, entry.getType(), entry.triggerTick, entry.priority);
-+ }
-+
-+ public void schedule(final BlockPos pos, final T data, final long targetTick, final TickPriority priority) {
-+ final TickNextTickData<T> entry = new TickNextTickData<>(pos, data, targetTick, priority);
-+ if (this.excludeFromScheduling.test(entry.getType())) {
-+ return;
-+ }
-+
-+ if (WARN_ON_EXCESSIVE_DELAY) {
-+ final long delay = entry.triggerTick - this.currentTick;
-+ if (delay >= EXCESSIVE_DELAY_THRESHOLD) {
-+ MinecraftServer.LOGGER.warn("Entry " + entry.toString() + " has been scheduled with an excessive delay of: " + delay, new Throwable());
-+ }
-+ }
-+
-+ final long blockKey = MCUtil.getBlockKey(pos);
-+
-+ final ArrayList<TickNextTickData<T>> currentEntries = this.entriesByBlock.computeIfAbsent(blockKey, (long keyInMap) -> new ArrayList<>(3));
-+
-+ if (currentEntries.isEmpty()) {
-+ currentEntries.add(entry);
-+ } else {
-+ for (int i = 0, size = currentEntries.size(); i < size; ++i) {
-+ final TickNextTickData<T> currentEntry = currentEntries.get(i);
-+
-+ // entries are only blocked from scheduling if currentEntry.equals(toSchedule) && currentEntry is scheduled to tick (NOT including pending)
-+ if (currentEntry.getType() == entry.getType() && currentEntry.tickState == STATE_SCHEDULED) {
-+ // can't add
-+ return;
-+ }
-+ }
-+ currentEntries.add(entry);
-+ }
-+
-+ entry.tickState = STATE_SCHEDULED;
-+
-+ this.entriesByChunk.computeIfAbsent(MCUtil.getCoordinateKey(entry.pos), (final long keyInMap) -> {
-+ return new ObjectRBTreeSet<>(TickListServerInterval.ENTRY_COMPARATOR);
-+ }).add(entry);
-+
-+ this.addToSchedule(entry);
-+ }
-+
-+ public void scheduleAll(final Iterator<TickNextTickData<T>> iterator) {
-+ while (iterator.hasNext()) {
-+ this.schedule(iterator.next());
-+ }
-+ }
-+
-+ // this is not the standard interception calculation, but it's the one vanilla uses
-+ // i.e the y value is ignored? the x, z calc isn't correct?
-+ // however for the copy op they use the correct intersection, after using this one of course...
-+ private static boolean isBlockInSortof(final BoundingBox boundingBox, final BlockPos pos) {
-+ return pos.getX() >= boundingBox.minX() && pos.getX() < boundingBox.maxX() && pos.getZ() >= boundingBox.minZ() && pos.getZ() < boundingBox.maxZ();
-+ }
-+
-+ @Override
-+ public List<TickNextTickData<T>> fetchTicksInArea(final BoundingBox structureboundingbox, final boolean removeReturned, final boolean excludeTicked) {
-+ if (structureboundingbox.minX() == structureboundingbox.maxX() || structureboundingbox.minZ() == structureboundingbox.maxZ()) {
-+ return Collections.emptyList(); // vanilla behaviour, check isBlockInSortof above
-+ }
-+
-+ final int lowerChunkX = structureboundingbox.minX() >> 4;
-+ final int upperChunkX = (structureboundingbox.maxX() - 1) >> 4; // subtract 1 since maxX is exclusive
-+ final int lowerChunkZ = structureboundingbox.minZ() >> 4;
-+ final int upperChunkZ = (structureboundingbox.maxZ() - 1) >> 4; // subtract 1 since maxZ is exclusive
-+
-+ final int xChunksLength = (upperChunkX - lowerChunkX + 1);
-+ final int zChunksLength = (upperChunkZ - lowerChunkZ + 1);
-+
-+ final ObjectRBTreeSet<TickNextTickData<T>>[] containingChunks = new ObjectRBTreeSet[xChunksLength * zChunksLength];
-+
-+ final int offset = (xChunksLength * -lowerChunkZ - lowerChunkX);
-+ int totalEntries = 0;
-+ for (int currChunkX = lowerChunkX; currChunkX <= upperChunkX; ++currChunkX) {
-+ for (int currChunkZ = lowerChunkZ; currChunkZ <= upperChunkZ; ++currChunkZ) {
-+ // todo optimize
-+ //final int index = (currChunkX - lowerChunkX) + xChunksLength * (currChunkZ - lowerChunkZ);
-+ final int index = offset + currChunkX + xChunksLength * currChunkZ;
-+ final ObjectRBTreeSet<TickNextTickData<T>> set = containingChunks[index] = this.entriesByChunk.get(MCUtil.getCoordinateKey(currChunkX, currChunkZ));
-+ if (set != null) {
-+ totalEntries += set.size();
-+ }
-+ }
-+ }
-+
-+ final List<TickNextTickData<T>> ret = new ArrayList<>(totalEntries);
-+
-+ final int matchOne = (STATE_SCHEDULED | STATE_PENDING_TICK) | (excludeTicked ? 0 : (STATE_TICKING | STATE_TICKED));
-+
-+ MCUtil.mergeSortedSets((TickNextTickData<T> entry) -> {
-+ if (!isBlockInSortof(structureboundingbox, entry.pos)) {
-+ return;
-+ }
-+ final int tickState = entry.tickState;
-+ if ((tickState & matchOne) == 0) {
-+ return;
-+ }
-+
-+ ret.add(entry);
-+ return;
-+ }, TickListServerInterval.ENTRY_COMPARATOR, containingChunks);
-+
-+ if (removeReturned) {
-+ for (TickNextTickData<T> entry : ret) {
-+ this.removeEntry(entry);
-+ }
-+ }
-+
-+ return ret;
-+ }
-+
-+ @Override
-+ public void copy(BoundingBox structureboundingbox, BlockPos blockposition) {
-+ // start copy from TickListServer // TODO check on update
-+ List<TickNextTickData<T>> list = this.fetchTicksInArea(structureboundingbox, false, false);
-+ Iterator<TickNextTickData<T>> iterator = list.iterator();
-+
-+ while (iterator.hasNext()) {
-+ TickNextTickData<T> nextticklistentry = iterator.next();
-+
-+ if (structureboundingbox.isInside( nextticklistentry.pos)) {
-+ BlockPos blockposition1 = nextticklistentry.pos.offset(blockposition);
-+ T t0 = nextticklistentry.getType();
-+
-+ this.schedule(new TickNextTickData<>(blockposition1, t0, nextticklistentry.triggerTick, nextticklistentry.priority));
-+ }
-+ }
-+ // end copy from TickListServer
-+ }
-+
-+ @Override
-+ public List<TickNextTickData<T>> fetchTicksInChunk(ChunkPos chunkPos, boolean removeReturned, boolean excludeTicked) {
-+ // Vanilla DOES get the entries 2 blocks out of the chunk too, but that doesn't matter since we ignore chunks
-+ // not at ticking status, and ticking status requires neighbours loaded
-+ // so with this method we will reduce scheduler churning
-+ final int matchOne = (STATE_SCHEDULED | STATE_PENDING_TICK) | (excludeTicked ? 0 : (STATE_TICKING | STATE_TICKED));
-+
-+ final ObjectRBTreeSet<TickNextTickData<T>> entries = this.entriesByChunk.get(MCUtil.getCoordinateKey(chunkPos));
-+
-+ if (entries == null) {
-+ return Collections.emptyList();
-+ }
-+
-+ final List<TickNextTickData<T>> ret = new ArrayList<>(entries.size());
-+
-+ for (TickNextTickData<T> entry : entries) {
-+ if ((entry.tickState & matchOne) == 0) {
-+ continue;
-+ }
-+ ret.add(entry);
-+ }
-+
-+ if (removeReturned) {
-+ for (TickNextTickData<T> entry : ret) {
-+ this.removeEntry(entry);
-+ }
-+ }
-+
-+ return ret;
-+ }
-+
-+ @Override
-+ public ListTag save(ChunkPos chunkcoordintpair) {
-+ // start copy from TickListServer // TODO check on update
-+ List<TickNextTickData<T>> list = this.fetchTicksInChunk(chunkcoordintpair, false, true);
-+
-+ return ServerTickList.saveTickList(this.getMinecraftKeyFrom, list, this.currentTick);
-+ // end copy from TickListServer
-+ }
-+
-+ @Override
-+ public int size() {
-+ // good thing this is only used in debug reports // TODO check on update
-+ int ret = 0;
-+
-+ for (TickNextTickData<T> entry : this.longScheduled) {
-+ if (entry.tickState == STATE_SCHEDULED) {
-+ ++ret;
-+ }
-+ }
-+
-+ for (Iterator<Long2ObjectMap.Entry<ArrayList<TickNextTickData<T>>>> iterator = this.pendingChunkTickLoad.long2ObjectEntrySet().iterator(); iterator.hasNext();) {
-+ ArrayList<TickNextTickData<T>> list = iterator.next().getValue();
-+
-+ for (TickNextTickData<T> entry : list) {
-+ if (entry.tickState == STATE_SCHEDULED) {
-+ ++ret;
-+ }
-+ }
-+ }
-+
-+ for (TickListServerInterval<T> interval : this.shortScheduled) {
-+ for (Iterable<TickNextTickData<T>> set : interval.byPriority) {
-+ for (TickNextTickData<T> entry : set) {
-+ if (entry.tickState == STATE_SCHEDULED) {
-+ ++ret;
-+ }
-+ }
-+ }
-+ }
-+
-+ return ret;
-+ }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/TickListServerInterval.java b/src/main/java/com/destroystokyo/paper/server/ticklist/TickListServerInterval.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..a1e6f49274a7ae8057a9112e0dd6597a8e58e6da
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/server/ticklist/TickListServerInterval.java
-@@ -0,0 +1,41 @@
-+package com.destroystokyo.paper.server.ticklist;
-+
-+import com.destroystokyo.paper.util.set.LinkedSortedSet;
-+import java.util.Comparator;
-+import net.minecraft.world.level.TickNextTickData;
-+import net.minecraft.world.level.TickPriority;
-+
-+// represents a set of entries to tick at a specified time
-+public final class TickListServerInterval<T> {
-+
-+ public static final int TOTAL_PRIORITIES = TickPriority.values().length;
-+ public static final Comparator<TickNextTickData<?>> ENTRY_COMPARATOR_BY_ID = (entry1, entry2) -> {
-+ return Long.compare(entry1.getId(), entry2.getId());
-+ };
-+ public static final Comparator<TickNextTickData<?>> ENTRY_COMPARATOR = (Comparator)TickNextTickData.createTimeComparator();
-+
-+ // we do not record the interval, this class is meant to be used on a ring buffer
-+
-+ // inlined enum map for TickListPriority
-+ public final LinkedSortedSet<TickNextTickData<T>>[] byPriority = new LinkedSortedSet[TOTAL_PRIORITIES];
-+
-+ {
-+ for (int i = 0, len = this.byPriority.length; i < len; ++i) {
-+ this.byPriority[i] = new LinkedSortedSet<>(ENTRY_COMPARATOR_BY_ID);
-+ }
-+ }
-+
-+ public void addEntryLast(final TickNextTickData<T> entry) {
-+ this.byPriority[entry.priority.ordinal()].addLast(entry);
-+ }
-+
-+ public void addEntryFirst(final TickNextTickData<T> entry) {
-+ this.byPriority[entry.priority.ordinal()].addFirst(entry);
-+ }
-+
-+ public void clear() {
-+ for (int i = 0, len = this.byPriority.length; i < len; ++i) {
-+ this.byPriority[i].clear(); // O(1) clear
-+ }
-+ }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/util/set/LinkedSortedSet.java b/src/main/java/com/destroystokyo/paper/util/set/LinkedSortedSet.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..118988c39e58f28e8a2851792b9c014f341f06fc
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/util/set/LinkedSortedSet.java
-@@ -0,0 +1,142 @@
-+package com.destroystokyo.paper.util.set;
-+
-+import java.util.Comparator;
-+import java.util.Iterator;
-+import java.util.NoSuchElementException;
-+
-+public final class LinkedSortedSet<E> implements Iterable<E> {
-+
-+ public final Comparator<? super E> comparator;
-+
-+ protected Link<E> head;
-+ protected Link<E> tail;
-+
-+ public LinkedSortedSet() {
-+ this((Comparator)Comparator.naturalOrder());
-+ }
-+
-+ public LinkedSortedSet(final Comparator<? super E> comparator) {
-+ this.comparator = comparator;
-+ }
-+
-+ public void clear() {
-+ this.head = this.tail = null;
-+ }
-+
-+ @Override
-+ public Iterator<E> iterator() {
-+ return new Iterator<E>() {
-+
-+ Link<E> next = LinkedSortedSet.this.head;
-+
-+ @Override
-+ public boolean hasNext() {
-+ return this.next != null;
-+ }
-+
-+ @Override
-+ public E next() {
-+ final Link<E> next = this.next;
-+ if (next == null) {
-+ throw new NoSuchElementException();
-+ }
-+ this.next = next.next;
-+ return next.element;
-+ }
-+ };
-+ }
-+
-+ public boolean addLast(final E element) {
-+ final Comparator<? super E> comparator = this.comparator;
-+
-+ Link<E> curr = this.tail;
-+ if (curr != null) {
-+ int compare;
-+
-+ while ((compare = comparator.compare(element, curr.element)) < 0) {
-+ Link<E> prev = curr;
-+ curr = curr.prev;
-+ if (curr != null) {
-+ continue;
-+ }
-+ this.head = prev.prev = new Link<>(element, null, prev);
-+ return true;
-+ }
-+
-+ if (compare != 0) {
-+ // insert after curr
-+ final Link<E> next = curr.next;
-+ final Link<E> insert = new Link<>(element, curr, next);
-+ curr.next = insert;
-+
-+ if (next == null) {
-+ this.tail = insert;
-+ } else {
-+ next.prev = insert;
-+ }
-+ return true;
-+ }
-+
-+ return false;
-+ } else {
-+ this.head = this.tail = new Link<>(element);
-+ return true;
-+ }
-+ }
-+
-+ public boolean addFirst(final E element) {
-+ final Comparator<? super E> comparator = this.comparator;
-+
-+ Link<E> curr = this.head;
-+ if (curr != null) {
-+ int compare;
-+
-+ while ((compare = comparator.compare(element, curr.element)) > 0) {
-+ Link<E> prev = curr;
-+ curr = curr.next;
-+ if (curr != null) {
-+ continue;
-+ }
-+ this.tail = prev.next = new Link<>(element, prev, null);
-+ return true;
-+ }
-+
-+ if (compare != 0) {
-+ // insert before curr
-+ final Link<E> prev = curr.prev;
-+ final Link<E> insert = new Link<>(element, prev, curr);
-+ curr.prev = insert;
-+
-+ if (prev == null) {
-+ this.head = insert;
-+ } else {
-+ prev.next = insert;
-+ }
-+ return true;
-+ }
-+
-+ return false;
-+ } else {
-+ this.head = this.tail = new Link<>(element);
-+ return true;
-+ }
-+ }
-+
-+ protected static final class Link<E> {
-+ public E element;
-+ public Link<E> prev;
-+ public Link<E> next;
-+
-+ public Link() {}
-+
-+ public Link(final E element) {
-+ this.element = element;
-+ }
-+
-+ public Link(final E element, final Link<E> prev, final Link<E> next) {
-+ this.element = element;
-+ this.prev = prev;
-+ this.next = next;
-+ }
-+ }
-+}
-diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 84f370e887a3e7ff49296bdf8d6d8de9cc194cfb..daeb5b175d17492f382d23af58a9cc46fbb49e60 100644
---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -86,6 +86,19 @@ public class ChunkHolder {
- return null;
- }
- // Paper end - no-tick view distance
-+ // Paper start
-+ public final boolean isEntityTickingReady() {
-+ return this.isEntityTickingReady;
-+ }
-+
-+ public final boolean isTickingReady() {
-+ return this.isTickingReady;
-+ }
-+
-+ public final boolean isFullChunkReady() {
-+ return this.isFullChunkReady;
-+ }
-+ // Paper end
-
- public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
- this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
-@@ -541,6 +554,11 @@ public class ChunkHolder {
- // Paper start - ticking chunk set
- ChunkHolder.this.chunkMap.level.getChunkSource().tickingChunks.add(chunk);
- // Paper end - ticking chunk set
-+ // Paper start - rewrite ticklistserver
-+ if (ChunkHolder.this.chunkMap.level.entityManager.areEntitiesLoaded(this.pos.longKey)) {
-+ ChunkHolder.this.chunkMap.level.onChunkSetTicking(ChunkHolder.this.pos.x, ChunkHolder.this.pos.z);
-+ }
-+ // Paper end - rewrite ticklistserver
- });
- });
- // Paper end
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index c6213a7dfcf9aeccdb546eaf74fa8eb119a6a32c..ec0d8e58a518a20634b902769251d6d04750433e 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -218,6 +218,18 @@ public class ServerChunkCache extends ChunkSource {
- }, this.mainThreadProcessor);
- }
- // Paper end
-+ // Paper start - rewrite ticklistserver
-+ public final boolean isPositionTickingReady(long pos) {
-+ final ChunkHolder chunkHolder = this.chunkMap.getUpdatingChunkIfPresent(pos);
-+ return chunkHolder != null && chunkHolder.isTickingReady();
-+ }
-+
-+ public final boolean isPositionTickingWithEntitiesLoaded(BlockPos pos) {
-+ final long position = net.minecraft.server.MCUtil.getCoordinateKey(pos);
-+ final ChunkHolder chunkHolder = this.chunkMap.getUpdatingChunkIfPresent(position);
-+ return chunkHolder != null && chunkHolder.isTickingReady() && this.level.entityManager.areEntitiesLoaded(position);
-+ }
-+ // Paper end - rewrite ticklistserver
-
- // Paper start
- // this will try to avoid chunk neighbours for lighting
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 527ae7af221c031b4cdf481f097e9062c41af5ac..a19b3c039751da14f044f05fb5ebfa08c051abe4 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -381,6 +381,15 @@ public class ServerLevel extends Level implements WorldGenLevel {
- }
- // Paper end
-
-+ // Paper start - rewrite ticklistserver
-+ public void onChunkSetTicking(int chunkX, int chunkZ) {
-+ if (com.destroystokyo.paper.PaperConfig.useOptimizedTickList) {
-+ ((com.destroystokyo.paper.server.ticklist.PaperTickList) this.blockTicks).onChunkSetTicking(chunkX, chunkZ);
-+ ((com.destroystokyo.paper.server.ticklist.PaperTickList) this.liquidTicks).onChunkSetTicking(chunkX, chunkZ);
-+ }
-+ }
-+ // Paper end - rewrite ticklistserver
-+
- // Add env and gen to constructor, WorldData -> WorldDataServer
- public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey<Level> resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List<CustomSpawner> list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen, org.bukkit.generator.BiomeProvider biomeProvider) {
- // Objects.requireNonNull(minecraftserver); // CraftBukkit - decompile error
-@@ -397,13 +406,19 @@ public class ServerLevel extends Level implements WorldGenLevel {
- DefaultedRegistry registryblocks = Registry.BLOCK;
-
- Objects.requireNonNull(registryblocks);
-- this.blockTicks = new ServerTickList<>(this, predicate, Registry.BLOCK::getKey, this::tickBlock, "Blocks"); // CraftBukkit - decompile error // Paper - Timings
-+ // this.blockTicks = new ServerTickList<>(this, predicate, Registry.BLOCK::getKey, this::tickBlock, "Blocks"); // CraftBukkit - decompile error // Paper - Timings // Paper - copied down
- Predicate<Fluid> predicate2 = (fluidtype) -> { // CraftBukkit - decompile error
- return fluidtype == null || fluidtype == Fluids.EMPTY;
- };
- registryblocks = Registry.FLUID;
- Objects.requireNonNull(registryblocks);
-+ if (com.destroystokyo.paper.PaperConfig.useOptimizedTickList) {
-+ this.blockTicks = new com.destroystokyo.paper.server.ticklist.PaperTickList<>(this, predicate, Registry.BLOCK::getKey, this::tickBlock, "Blocks"); // Paper - Timings
-+ this.liquidTicks = new com.destroystokyo.paper.server.ticklist.PaperTickList<>(this, predicate2, Registry.FLUID::getKey, this::tickLiquid, "Fluids"); // Paper timings
-+ } else {
-+ this.blockTicks = new ServerTickList<>(this, predicate, Registry.BLOCK::getKey, this::tickBlock, "Blocks"); // CraftBukkit - decompile error // Paper - Timings & copied from above
- this.liquidTicks = new ServerTickList<>(this, predicate2, Registry.FLUID::getKey, this::tickLiquid, "Fluids"); // CraftBukkit - decompile error // Paper - Timings
-+ }
- this.navigatingMobs = new ObjectOpenHashSet();
- this.blockEvents = new ObjectLinkedOpenHashSet();
- this.dragonParts = new Int2ObjectOpenHashMap();
-@@ -689,7 +704,9 @@ public class ServerLevel extends Level implements WorldGenLevel {
- if (this.tickTime) {
- long i = this.levelData.getGameTime() + 1L;
-
-- this.serverLevelData.setGameTime(i);
-+ this.serverLevelData.setGameTime(i); ; // Paper - diff on change, we want the below to be ran right after this
-+ this.blockTicks.nextTick(); // Paper
-+ this.liquidTicks.nextTick(); // Paper
- this.serverLevelData.getScheduledEvents().tick(this.server, i);
- if (this.levelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) {
- this.setDayTime(this.levelData.getDayTime() + 1L);
-diff --git a/src/main/java/net/minecraft/world/level/ChunkTickList.java b/src/main/java/net/minecraft/world/level/ChunkTickList.java
-index 1a05edf041cf4aeee7c165fec564ce45adbdd5c7..febc837d324cbe2cd83aea6c1e0d298c70f45f78 100644
---- a/src/main/java/net/minecraft/world/level/ChunkTickList.java
-+++ b/src/main/java/net/minecraft/world/level/ChunkTickList.java
-@@ -15,7 +15,7 @@ public class ChunkTickList<T> implements TickList<T> {
-
- public ChunkTickList(Function<T, ResourceLocation> identifierProvider, List<TickNextTickData<T>> scheduledTicks, long startTime) {
- this(identifierProvider, scheduledTicks.stream().map((tickNextTickData) -> {
-- return new ChunkTickList.ScheduledTick(tickNextTickData.getType(), tickNextTickData.pos, (int)(tickNextTickData.triggerTick - startTime), tickNextTickData.priority);
-+ return new ChunkTickList.ScheduledTick<>(tickNextTickData.getType(), tickNextTickData.pos, (int)(tickNextTickData.triggerTick - startTime), tickNextTickData.priority); // Paper - decompile error
- }).collect(Collectors.toList()));
- }
-
-@@ -56,6 +56,7 @@ public class ChunkTickList<T> implements TickList<T> {
- return listTag;
- }
-
-+ private static final int MAX_TICK_DELAY = Integer.getInteger("paper.ticklist-max-tick-delay", -1).intValue(); // Paper - clean up broken entries
- public static <T> ChunkTickList<T> create(ListTag ticks, Function<T, ResourceLocation> function, Function<ResourceLocation, T> function2) {
- List<ChunkTickList.ScheduledTick<T>> list = Lists.newArrayList();
-
-@@ -64,7 +65,14 @@ public class ChunkTickList<T> implements TickList<T> {
- T object = function2.apply(new ResourceLocation(compoundTag.getString("i")));
- if (object != null) {
- BlockPos blockPos = new BlockPos(compoundTag.getInt("x"), compoundTag.getInt("y"), compoundTag.getInt("z"));
-- list.add(new ChunkTickList.ScheduledTick<>(object, blockPos, compoundTag.getInt("t"), TickPriority.byValue(compoundTag.getInt("p"))));
-+ // Paper start - clean up broken entries
-+ int delay = compoundTag.getInt("t");
-+ if (MAX_TICK_DELAY > 0 && delay > MAX_TICK_DELAY) {
-+ net.minecraft.server.MinecraftServer.LOGGER.warn("Dropping tick for pos " + blockPos + ", tick delay " + delay);
-+ continue;
-+ }
-+ // Paper end - clean up broken entries
-+ list.add(new ChunkTickList.ScheduledTick<>(object, blockPos, delay, TickPriority.byValue(compoundTag.getInt("p"))));
- }
- }
-
-diff --git a/src/main/java/net/minecraft/world/level/ServerTickList.java b/src/main/java/net/minecraft/world/level/ServerTickList.java
-index dfd41db804acde50339de9b18566b845401d7cbe..0f61dd3a3115f68ccd2c7cb4c9309f5ace96a406 100644
---- a/src/main/java/net/minecraft/world/level/ServerTickList.java
-+++ b/src/main/java/net/minecraft/world/level/ServerTickList.java
-@@ -51,6 +51,9 @@ public class ServerTickList<T> implements TickList<T> {
- this.ticker = tickConsumer;
- }
-
-+ // Paper start
-+ public void nextTick() {}
-+ // Paper end
- public void tick() {
- int i = this.tickNextTickList.size();
-
-diff --git a/src/main/java/net/minecraft/world/level/TickNextTickData.java b/src/main/java/net/minecraft/world/level/TickNextTickData.java
-index 3b8c04f6ffd7e6c197465aa1caf633ba92529472..1007bfc9c19641f42afd5526cfe7bdb61906d1a0 100644
---- a/src/main/java/net/minecraft/world/level/TickNextTickData.java
-+++ b/src/main/java/net/minecraft/world/level/TickNextTickData.java
-@@ -9,7 +9,9 @@ public class TickNextTickData<T> {
- public final BlockPos pos;
- public final long triggerTick;
- public final TickPriority priority;
-- private final long c;
-+ private final long c; @Deprecated public final long getId() { return this.c; } // Paper - OBFHELPER
-+ private final int hash; // Paper
-+ public int tickState; // Paper
-
- public TickNextTickData(BlockPos pos, T t) {
- this(pos, t, 0L, TickPriority.NORMAL);
-@@ -21,6 +23,7 @@ public class TickNextTickData<T> {
- this.type = t;
- this.triggerTick = time;
- this.priority = priority;
-+ this.hash = this.computeHash(); // Paper
- }
-
- @Override
-@@ -35,17 +38,27 @@ public class TickNextTickData<T> {
-
- @Override
- public int hashCode() {
-+ // Paper start - optimize hashcode
-+ return this.hash;
-+ }
-+ public final int computeHash() {
-+ // Paper end - optimize hashcode
- return this.pos.hashCode();
- }
-
- public static <T> Comparator<TickNextTickData<T>> createTimeComparator() {
-- return Comparator.<TickNextTickData<T>>comparingLong((tickNextTickData) -> { // Paper - decompile fix
-- return tickNextTickData.triggerTick;
-- }).thenComparing((tickNextTickData) -> {
-- return tickNextTickData.priority;
-- }).thenComparingLong((tickNextTickData) -> {
-- return tickNextTickData.c;
-- });
-+ // Paper start - let's not use more functional code for no reason.
-+ return (Comparator) (Comparator<TickNextTickData>) (TickNextTickData nextticklistentry, TickNextTickData nextticklistentry1) -> {
-+ int i = Long.compare(nextticklistentry.triggerTick, nextticklistentry1.triggerTick);
-+
-+ if (i != 0) {
-+ return i;
-+ } else {
-+ i = nextticklistentry.priority.compareTo(nextticklistentry1.priority);
-+ return i != 0 ? i : Long.compare(nextticklistentry.getId(), nextticklistentry1.getId());
-+ }
-+ };
-+ // Paper end - let's not use more functional code for no reason.
- }
-
- @Override
-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 f698723f8f7feecc749df10a316118184391f31a..be65a8a5a853d4e014d44730a48ccf247acf08d2 100644
---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
-+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
-@@ -308,6 +308,12 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
- List<Entity> entities = this.getEntities(chunkentities.getPos()); // PAIL rename getChunkPos
- CraftEventFactory.callEntitiesLoadEvent(((EntityStorage) this.permanentStorage).level, chunkentities.getPos(), entities);
- // CraftBukkit end
-+ // Paper start - rewrite ServerTickList
-+ final net.minecraft.server.level.ServerLevel level = ((net.minecraft.world.level.chunk.storage.EntityStorage) this.permanentStorage).level;
-+ if (level.chunkSource.isPositionTickingReady(chunkentities.getPos().longKey)) {
-+ level.onChunkSetTicking(chunkentities.getPos().x, chunkentities.getPos().z);
-+ }
-+ // Paper end
- }
-
- }
diff --git a/patches/removed/1.18/0436-Optimize-sending-packets-to-nearby-locations-sounds-.patch b/patches/removed/1.18/0436-Optimize-sending-packets-to-nearby-locations-sounds-.patch
deleted file mode 100644
index 9d47f45f09..0000000000
--- a/patches/removed/1.18/0436-Optimize-sending-packets-to-nearby-locations-sounds-.patch
+++ /dev/null
@@ -1,63 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 23 May 2020 17:03:41 -0400
-Subject: [PATCH] Optimize sending packets to nearby locations (sounds/effects)
-
-Instead of using the entire world or player list, use the distance
-maps to only iterate players who are even seeing the chunk the packet
-is originating from.
-
-This will drastically cut down on packet sending cost for worlds with
-lots of players in them.
-
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index c2cc59f476a67224774503009439fec2ad1ec62d..54cbcea8e3eb4d2fdeced2ebc3c4819af429da9d 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -1134,16 +1134,40 @@ public abstract class PlayerList {
- }
-
- public void broadcast(@Nullable net.minecraft.world.entity.player.Player player, double x, double y, double z, double distance, ResourceKey<Level> worldKey, Packet<?> packet) {
-- for (int i = 0; i < this.players.size(); ++i) {
-- ServerPlayer entityplayer = (ServerPlayer) this.players.get(i);
-+ ServerLevel world = null;
-+ if (player != null && player.level instanceof ServerLevel) {
-+ world = (ServerLevel) player.level;
-+ }
-
-- // CraftBukkit start - Test if player receiving packet can see the source of the packet
-- if (player != null && player instanceof ServerPlayer && !entityplayer.getBukkitEntity().canSee(((ServerPlayer) player).getBukkitEntity())) {
-- continue;
-+ // Paper start
-+ if (world == null) {
-+ world = server.getLevel(worldKey);
-+ }
-+ net.minecraft.server.level.ChunkMap chunkMap = world != null ? world.getChunkSource().chunkMap : null;
-+ Object[] backingSet;
-+ if (chunkMap == null) {
-+ // Really shouldn't happen...
-+ backingSet = world != null ? world.players.toArray() : players.toArray();
-+ } else {
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> nearbyPlayers = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.fastFloor(x) >> 4, MCUtil.fastFloor(z) >> 4);
-+ if (nearbyPlayers == null) {
-+ return;
- }
-+ backingSet = nearbyPlayers.getBackingSet();
-+ }
-+
-+ for (Object object : backingSet) {
-+ if (!(object instanceof ServerPlayer)) continue;
-+ ServerPlayer entityplayer = (ServerPlayer) object;
-+ // Paper end
-+
-+ // CraftBukkit start - Test if player receiving packet can see the source of the packet
-+ //if (entityhuman != null && entityhuman instanceof EntityPlayer && !entityplayer.getBukkitEntity().canSee(((EntityPlayer) entityhuman).getBukkitEntity())) { // Paper
-+ //continue; // Paper
-+ //} // Paper
- // CraftBukkit end
-
-- if (entityplayer != player && entityplayer.level.dimension() == worldKey) {
-+ if (entityplayer != player && entityplayer.level.dimension() == worldKey && (!(player instanceof ServerPlayer) || entityplayer.getBukkitEntity().canSee(((ServerPlayer) player).getBukkitEntity()))) { // Paper
- double d4 = x - entityplayer.getX();
- double d5 = y - entityplayer.getY();
- double d6 = z - entityplayer.getZ();
diff --git a/patches/removed/1.18/0445-Optimize-WorldBorder-collision-checks-and-air.patch b/patches/removed/1.18/0445-Optimize-WorldBorder-collision-checks-and-air.patch
deleted file mode 100644
index 65d3ef949d..0000000000
--- a/patches/removed/1.18/0445-Optimize-WorldBorder-collision-checks-and-air.patch
+++ /dev/null
@@ -1,53 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sun, 10 May 2020 22:49:05 -0400
-Subject: [PATCH] Optimize WorldBorder collision checks and air
-
-
-# 1.18: All 3 hunks don't seem to apply anymore.
-(CollisionSpliterator renamed, kinda, to BlockCollisions)
-
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 890502222191ce1ab5a598bf040fc462fc915e31..19a6b2c686e6421624282d0536dfdd3320da4ec6 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -1070,7 +1070,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n
- AABB axisalignedbb = this.getBoundingBox();
- CollisionContext voxelshapecollision = CollisionContext.of(this);
- VoxelShape voxelshape = this.level.getWorldBorder().getCollisionShape();
-- Stream<VoxelShape> stream = Shapes.joinIsNotEmpty(voxelshape, Shapes.create(axisalignedbb.deflate(1.0E-7D)), BooleanOp.AND) ? Stream.empty() : Stream.of(voxelshape);
-+ Stream<VoxelShape> stream = !this.level.getWorldBorder().isWithinBounds(axisalignedbb) ? Stream.empty() : Stream.of(voxelshape); // Paper
- Stream<VoxelShape> stream1 = this.level.getEntityCollisions(this, axisalignedbb.expandTowards(movement), (entity) -> {
- return true;
- });
-diff --git a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java
-index e4122469b839103f5c0fce38822d408a903dc0a5..6124e3a32325e8c74bf839010a79d7c82c49aaff 100644
---- a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java
-+++ b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java
-@@ -140,9 +140,10 @@ public class CollisionSpliterator extends AbstractSpliterator<VoxelShape> {
- WorldBorder worldBorder = this.collisionGetter.getWorldBorder();
- AABB aABB = this.source.getBoundingBox();
- if (!isBoxFullyWithinWorldBorder(worldBorder, aABB)) {
-- VoxelShape voxelShape = worldBorder.getCollisionShape();
-- if (!isOutsideBorder(voxelShape, aABB) && isCloseToBorder(voxelShape, aABB)) {
-- action.accept(voxelShape);
-+ // Paper start
-+ if (worldBorder.isWithinBounds(aABB.deflate(1.0E-7D)) && !worldBorder.isWithinBounds(aABB.inflate(1.0E-7D))) {
-+ action.accept(worldBorder.getCollisionShape());
-+ // Paper end
- return true;
- }
- }
-diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
-index 18eeb49a4859a8ab9cbef97caf63c0639bc63233..16bc18cacbf7a23fb744c8a12e7fd8da699b2fea 100644
---- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
-+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java
-@@ -239,7 +239,7 @@ public final class Shapes {
- mutableBlockPos.set(axisCycle, q, r, p);
- BlockState blockState = world.getTypeIfLoaded(mutableBlockPos); // Paper
- if (blockState == null) return 0.0D; // Paper
-- if ((s != 1 || blockState.hasLargeCollisionShape()) && (s != 2 || blockState.is(Blocks.MOVING_PISTON))) {
-+ if (!blockState.isAir() && (s != 1 || blockState.hasLargeCollisionShape()) && (s != 2 || blockState.is(Blocks.MOVING_PISTON))) { // Paper
- initial = blockState.getCollisionShape(world, mutableBlockPos, context).collide(axis3, box.move((double)(-mutableBlockPos.getX()), (double)(-mutableBlockPos.getY()), (double)(-mutableBlockPos.getZ())), initial);
- if (Math.abs(initial) < 1.0E-7D) {
- return 0.0D;
diff --git a/patches/removed/1.18/0793-Fix-Codec-log-spam.patch b/patches/removed/1.18/0793-Fix-Codec-log-spam.patch
deleted file mode 100644
index 56249884d3..0000000000
--- a/patches/removed/1.18/0793-Fix-Codec-log-spam.patch
+++ /dev/null
@@ -1,197 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sat, 19 Jun 2021 10:54:52 -0700
-Subject: [PATCH] Fix Codec log spam
-
-Mojang did NOT add dataconverters for world gen configurations
-that they CHANGED. So, the codec fails to parse old data.
-
-This fixes two instances:
-- IntProvider is new and Mojang did not account for old data.
- Thankfully, only ColumnPlace needed to be special cased.
-- TreeConfiguration had changes. Thankfully, they were
- only renames for one value and thankfully defaults could
- be provided for two new values (WITHOUT changing behavior).
-
-diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
-index c4117dcffd705d044f07eb5840a177b1b5825bb9..f80791bd383dc6dc4a9c1aac5f8e4c1561e33ad9 100644
---- a/src/main/java/net/minecraft/server/MCUtil.java
-+++ b/src/main/java/net/minecraft/server/MCUtil.java
-@@ -723,4 +723,70 @@ public final class MCUtil {
- public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) {
- return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status);
- }
-+
-+ public static <A> com.mojang.serialization.MapCodec<A> fieldWithFallbacks(com.mojang.serialization.Codec<A> codec, String name, String ...fallback) {
-+ return com.mojang.serialization.MapCodec.of(
-+ new com.mojang.serialization.codecs.FieldEncoder<>(name, codec),
-+ new FieldFallbackDecoder<>(name, java.util.Arrays.asList(fallback), codec),
-+ () -> "FieldFallback[" + name + ": " + codec.toString() + "]"
-+ );
-+ }
-+
-+ // This is likely a common occurrence, sadly
-+ public static final class FieldFallbackDecoder<A> extends com.mojang.serialization.MapDecoder.Implementation<A> {
-+ protected final String name;
-+ protected final List<String> fallback;
-+ private final com.mojang.serialization.Decoder<A> elementCodec;
-+
-+ public FieldFallbackDecoder(final String name, final List<String> fallback, final com.mojang.serialization.Decoder<A> elementCodec) {
-+ this.name = name;
-+ this.fallback = fallback;
-+ this.elementCodec = elementCodec;
-+ }
-+
-+ @Override
-+ public <T> com.mojang.serialization.DataResult<A> decode(final com.mojang.serialization.DynamicOps<T> ops, final com.mojang.serialization.MapLike<T> input) {
-+ T value = input.get(name);
-+ if (value == null) {
-+ for (String fall : fallback) {
-+ value = input.get(fall);
-+ if (value != null) {
-+ break;
-+ }
-+ }
-+ if (value == null) {
-+ return com.mojang.serialization.DataResult.error("No key " + name + " in " + input);
-+ }
-+ }
-+ return elementCodec.parse(ops, value);
-+ }
-+
-+ @Override
-+ public <T> java.util.stream.Stream<T> keys(final com.mojang.serialization.DynamicOps<T> ops) {
-+ return java.util.stream.Stream.of(ops.createString(name));
-+ }
-+
-+ @Override
-+ public boolean equals(final Object o) {
-+ if (this == o) {
-+ return true;
-+ }
-+ if (o == null || getClass() != o.getClass()) {
-+ return false;
-+ }
-+ final FieldFallbackDecoder<?> that = (FieldFallbackDecoder<?>)o;
-+ return java.util.Objects.equals(name, that.name) && java.util.Objects.equals(elementCodec, that.elementCodec)
-+ && java.util.Objects.equals(fallback, that.fallback);
-+ }
-+
-+ @Override
-+ public int hashCode() {
-+ return java.util.Objects.hash(name, fallback, elementCodec);
-+ }
-+
-+ @Override
-+ public String toString() {
-+ return "FieldDecoder[" + name + ": " + elementCodec + ']';
-+ }
-+ }
- }
-diff --git a/src/main/java/net/minecraft/util/valueproviders/IntProvider.java b/src/main/java/net/minecraft/util/valueproviders/IntProvider.java
-index 020a19cd683dd3779c5116d12b3cdcd3b3ca69b4..c81a0eec12436c10869bfdcc21af09173f438331 100644
---- a/src/main/java/net/minecraft/util/valueproviders/IntProvider.java
-+++ b/src/main/java/net/minecraft/util/valueproviders/IntProvider.java
-@@ -9,13 +9,44 @@ import net.minecraft.core.Registry;
-
- public abstract class IntProvider {
- private static final Codec<Either<Integer, IntProvider>> CONSTANT_OR_DISPATCH_CODEC = Codec.either(Codec.INT, Registry.INT_PROVIDER_TYPES.dispatch(IntProvider::getType, IntProviderType::codec));
-- public static final Codec<IntProvider> CODEC = CONSTANT_OR_DISPATCH_CODEC.xmap((either) -> {
-+ public static final Codec<IntProvider> CODEC_REAL = CONSTANT_OR_DISPATCH_CODEC.xmap((either) -> { // Paper - used by CODEC below
- return either.map(ConstantInt::of, (intProvider) -> {
- return intProvider;
- });
- }, (intProvider) -> {
- return intProvider.getType() == IntProviderType.CONSTANT ? Either.left(((ConstantInt)intProvider).getValue()) : Either.right(intProvider);
- });
-+ // Paper start
-+ public static final Codec<IntProvider> CODEC = new Codec<>() {
-+ @Override
-+ public <T> DataResult<com.mojang.datafixers.util.Pair<IntProvider, T>> decode(com.mojang.serialization.DynamicOps<T> ops, T input) {
-+ /*
-+ UniformInt:
-+ count -> { (old format)
-+ base, spread
-+ } -> {UniformInt} { (new format & type)
-+ base, base + spread
-+ } */
-+
-+
-+ if (ops.get(input, "base").result().isPresent() && ops.get(input, "spread").result().isPresent()) {
-+ // detected old format
-+ int base = ops.getNumberValue(ops.get(input, "base").result().get()).result().get().intValue();
-+ int spread = ops.getNumberValue(ops.get(input, "spread").result().get()).result().get().intValue();
-+ return DataResult.success(new com.mojang.datafixers.util.Pair<>(UniformInt.of(base, base + spread), input));
-+ }
-+
-+ // not old format, forward to real codec
-+ return CODEC_REAL.decode(ops, input);
-+ }
-+
-+ @Override
-+ public <T> DataResult<T> encode(IntProvider input, com.mojang.serialization.DynamicOps<T> ops, T prefix) {
-+ // forward to real codec
-+ return CODEC_REAL.encode(input, ops, prefix);
-+ }
-+ };
-+ // Paper end
- public static final Codec<IntProvider> NON_NEGATIVE_CODEC = codec(0, Integer.MAX_VALUE);
- public static final Codec<IntProvider> POSITIVE_CODEC = codec(1, Integer.MAX_VALUE);
-
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java b/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java
-index 05bba5410fbd9f8e333584ccbd65a909f3040322..82859e1b048ba8a96cc67e085e9ed01b6bd3a6cd 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java
-@@ -10,11 +10,28 @@ import net.minecraft.world.level.LevelAccessor;
- import net.minecraft.world.level.block.state.BlockState;
-
- public class ColumnPlacer extends BlockPlacer {
-+ // Paper start
- public static final Codec<ColumnPlacer> CODEC = RecordCodecBuilder.create((instance) -> {
-- return instance.group(IntProvider.NON_NEGATIVE_CODEC.fieldOf("size").forGetter((columnPlacer) -> {
-- return columnPlacer.size;
-- })).apply(instance, ColumnPlacer::new);
-+ return instance.group(
-+ IntProvider.NON_NEGATIVE_CODEC.optionalFieldOf("size").forGetter((columnPlacer) -> {
-+ return java.util.Optional.of(columnPlacer.size);
-+ }),
-+ Codec.INT.optionalFieldOf("min_size").forGetter((columnPlacer) -> {
-+ return java.util.Optional.empty();
-+ }),
-+ Codec.INT.optionalFieldOf("extra_size").forGetter((columnPlacer) -> {
-+ return java.util.Optional.empty();
-+ })
-+ ).apply(instance, ColumnPlacer::new);
- });
-+ public ColumnPlacer(java.util.Optional<IntProvider> size, java.util.Optional<Integer> minSize, java.util.Optional<Integer> extraSize) {
-+ if (size.isPresent()) {
-+ this.size = size.get();
-+ } else {
-+ this.size = net.minecraft.util.valueproviders.BiasedToBottomInt.of(minSize.get().intValue(), minSize.get().intValue() + extraSize.get().intValue());
-+ }
-+ }
-+ // Paper end
- private final IntProvider size;
-
- public ColumnPlacer(IntProvider size) {
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java b/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java
-index 5da68897148192905c2747676c1ee2ee649f923f..b990099cf274f8cb0d96c139345cf0bf328affd6 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java
-@@ -18,13 +18,13 @@ public class TreeConfiguration implements FeatureConfiguration {
- return treeConfiguration.trunkProvider;
- }), TrunkPlacer.CODEC.fieldOf("trunk_placer").forGetter((treeConfiguration) -> {
- return treeConfiguration.trunkPlacer;
-- }), BlockStateProvider.CODEC.fieldOf("foliage_provider").forGetter((treeConfiguration) -> {
-+ }), net.minecraft.server.MCUtil.fieldWithFallbacks(BlockStateProvider.CODEC, "foliage_provider", "leaves_provider").forGetter((treeConfiguration) -> { // Paper - provide fallback for rename
- return treeConfiguration.foliageProvider;
-- }), BlockStateProvider.CODEC.fieldOf("sapling_provider").forGetter((treeConfiguration) -> {
-+ }), BlockStateProvider.CODEC.optionalFieldOf("sapling_provider", new SimpleStateProvider(Blocks.OAK_SAPLING.defaultBlockState())).forGetter((treeConfiguration) -> { // Paper - provide default - it looks like for now this is OK because it's just used to check canSurvive. Same check happens in 1.16.5 for the default we provide - so it should retain behavior...
- return treeConfiguration.saplingProvider;
- }), FoliagePlacer.CODEC.fieldOf("foliage_placer").forGetter((treeConfiguration) -> {
- return treeConfiguration.foliagePlacer;
-- }), BlockStateProvider.CODEC.fieldOf("dirt_provider").forGetter((treeConfiguration) -> {
-+ }), BlockStateProvider.CODEC.optionalFieldOf("dirt_provider", new SimpleStateProvider(Blocks.DIRT.defaultBlockState())).forGetter((treeConfiguration) -> { // Paper - provide defaults, old data DOES NOT have this key (thankfully ALL OLD DATA used DIRT)
- return treeConfiguration.dirtProvider;
- }), FeatureSize.CODEC.fieldOf("minimum_size").forGetter((treeConfiguration) -> {
- return treeConfiguration.minimumSize;
diff --git a/patches/removed/1.18/0836-Cache-palette-array.patch b/patches/removed/1.18/0836-Cache-palette-array.patch
deleted file mode 100644
index 6ae3d7da68..0000000000
--- a/patches/removed/1.18/0836-Cache-palette-array.patch
+++ /dev/null
@@ -1,64 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Paul Sauve <[email protected]>
-Date: Thu, 4 Feb 2021 23:28:46 -0600
-Subject: [PATCH] Cache palette array
-
-Instead of allocating the 4KB for every chunk section, cache it locally and
-reuse it for other chunk sections to save on allocations. These allocations add
-up very quickly when saving chunks frequently.
-
-1.18: PalettedContainer changed, mojang introduced a Codec into the mix to make things
-more complicated. Needs a complete rework, but probably still viable as a perf improvement.
-
-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 c9e942669458668a184aaec3bc0a5509dd6ab5f0..ac84d49d7819666a89cacbe9ef1199cf22ac2ac3 100644
---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java
-@@ -263,13 +263,17 @@ public class PalettedContainer<T> implements PaletteResize<T> {
-
- }
-
-+ // Paper start - allow reusing int array
- public synchronized void write(CompoundTag nbt, String paletteKey, String dataKey) { // Paper - synchronize
-+ this.write(nbt, paletteKey, dataKey, new int[4096]);
-+ }
-+ public synchronized void write(CompoundTag nbt, String paletteKey, String dataKey, int[] is) { // Paper - synchronize // Paper end
- try {
- this.acquire();
- HashMapPalette<T> hashMapPalette = new HashMapPalette<>(this.registry, this.bits, this.dummyPaletteResize, this.reader, this.writer);
- T object = this.defaultValue;
- int i = hashMapPalette.idFor(this.defaultValue);
-- int[] is = new int[4096];
-+ //int[] is = new int[4096]; // Paper - will now be pulled from ThreadLocal
-
- for(int j = 0; j < 4096; ++j) {
- T object2 = this.get(j);
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-index 7921ee2786d0d6a60d43786b20efc03a0f9178e3..10f15b8c7763a980aec47a4b4dda5686e60642ce 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-@@ -500,6 +500,7 @@ public class ChunkSerializer {
- return new AsyncSaveData(blockLight, skyLight, blockTickListSerialized, fluidTickListSerialized, blockEntitiesSerialized, world.getGameTime());
- }
-
-+ private static final ThreadLocal<int[]> PALETTE_ARRAY = ThreadLocal.withInitial(() -> new int[4096]); // Paper - maintain a per-thread cache for saving palettes
- public static CompoundTag write(ServerLevel world, ChunkAccess chunk) {
- return saveChunk(world, chunk, null);
- }
-@@ -533,6 +534,7 @@ public class ChunkSerializer {
- ThreadedLevelLightEngine lightenginethreaded = world.getChunkSource().getLightEngine();
- boolean flag = chunk.isLightCorrect();
-
-+ int[] is = PALETTE_ARRAY.get(); // Paper - use cached
- for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) {
- int finalI = i; // CraftBukkit - decompile errors
- LevelChunkSection chunksection = (LevelChunkSection) Arrays.stream(achunksection).filter((chunksection1) -> {
-@@ -547,7 +549,7 @@ public class ChunkSerializer {
-
- nbttagcompound2.putByte("Y", (byte) (i & 255));
- if (chunksection != LevelChunk.EMPTY_SECTION) {
-- chunksection.getStates().write(nbttagcompound2, "Palette", "BlockStates");
-+ chunksection.getStates().write(nbttagcompound2, "Palette", "BlockStates", is); // Paper - reuse array
- }
-
- // Paper start - replace light engine
diff --git a/patches/removed/1.18/No longer needed/0078-Fix-reducedDebugInfo-not-initialized-on-client.patch b/patches/removed/1.18/No longer needed/0078-Fix-reducedDebugInfo-not-initialized-on-client.patch
deleted file mode 100644
index 5ef0fe3665..0000000000
--- a/patches/removed/1.18/No longer needed/0078-Fix-reducedDebugInfo-not-initialized-on-client.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jedediah Smith <[email protected]>
-Date: Sat, 2 Apr 2016 20:37:03 -0400
-Subject: [PATCH] Fix reducedDebugInfo not initialized on client
-
-
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 0ea5574ab43141a69e6aa5e05aeb548660f126e1..b2638a7dbe2ead274e9e2e337babe41c90179f8b 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -249,6 +249,7 @@ public abstract class PlayerList {
- playerconnection.send(new ClientboundSetCarriedItemPacket(player.getInventory().selected));
- playerconnection.send(new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes()));
- playerconnection.send(new ClientboundUpdateTagsPacket(this.server.getTags().serializeToNetwork((RegistryAccess) this.registryHolder)));
-+ playerconnection.send(new ClientboundEntityEventPacket(player, (byte) (worldserver1.getGameRules().getBoolean(GameRules.RULE_REDUCEDDEBUGINFO) ? 22 : 23))); // Paper - fix this rule not being initialized on the client
- this.sendPlayerPermissionLevel(player);
- player.getStats().markAllDirty();
- player.getRecipeBook().sendInitialRecipeBook(player);
diff --git a/patches/removed/1.18/No longer needed/0170-Make-max-squid-spawn-height-configurable.patch b/patches/removed/1.18/No longer needed/0170-Make-max-squid-spawn-height-configurable.patch
deleted file mode 100644
index 7dd91ec111..0000000000
--- a/patches/removed/1.18/No longer needed/0170-Make-max-squid-spawn-height-configurable.patch
+++ /dev/null
@@ -1,37 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Zach Brown <[email protected]>
-Date: Thu, 11 Jan 2018 16:47:28 -0600
-Subject: [PATCH] Make max squid spawn height configurable
-
-#NOTE: Spigot removed the min option, Vanilla now has the same spawn rule for all ambient water animals
-I don't know why upstream made only the minimum height configurable but
-whatever
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index e2894138d3efb32161087ad2a1093b8c15c56a65..5892823425055efb92bf635b035d62981942b966 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -383,4 +383,9 @@ public class PaperWorldConfig {
- disableCreeperLingeringEffect = getBoolean("disable-creeper-lingering-effect", false);
- log("Creeper lingering effect: " + disableCreeperLingeringEffect);
- }
-+
-+ public double squidMaxSpawnHeight;
-+ private void squidMaxSpawnHeight() {
-+ squidMaxSpawnHeight = getDouble("squid-spawn-height.maximum", 0.0D);
-+ }
- }
-diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java
-index 3ffc1ee8a9ae63c8678c12736fab5d6ba0a21a5b..4da560f6e4da0750bda78b900b2d916d58adfccb 100644
---- a/src/main/java/net/minecraft/world/entity/animal/Squid.java
-+++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java
-@@ -211,7 +211,8 @@ public class Squid extends WaterAnimal {
- }
-
- public static boolean checkSquidSpawnRules(EntityType<Squid> type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random) {
-- return pos.getY() > world.getMinecraftWorld().spigotConfig.squidSpawnRangeMin && pos.getY() < world.getSeaLevel(); // Spigot
-+ final double maxHeight = world.getMinecraftWorld().paperConfig.squidMaxSpawnHeight > 0 ? world.getMinecraftWorld().paperConfig.squidMaxSpawnHeight : world.getSeaLevel(); // Paper
-+ return pos.getY() > world.getMinecraftWorld().spigotConfig.squidSpawnRangeMin && pos.getY() < maxHeight; // Spigot // Paper
- }
-
- @Override
diff --git a/patches/removed/1.18/No longer needed/0228-Optimize-IntIdentityHashBiMiap-nextId.patch b/patches/removed/1.18/No longer needed/0228-Optimize-IntIdentityHashBiMiap-nextId.patch
deleted file mode 100644
index f1447869ec..0000000000
--- a/patches/removed/1.18/No longer needed/0228-Optimize-IntIdentityHashBiMiap-nextId.patch
+++ /dev/null
@@ -1,68 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Andrew Steinborn <[email protected]>
-Date: Mon, 23 Jul 2018 13:08:19 -0400
-Subject: [PATCH] Optimize IntIdentityHashBiMiap#nextId()
-
-Optimizes CrudeIncrementalIntIdentityHashBiMap#nextId()
-
-This is a frequent hotspot for world loading/saving.
-
-1.18 note: seems like the bitset is too small now? removing this patch fixed
-lots of issues related to synchedentitydata and structure gen. when asked, Tux said "nuke it"
-
-diff --git a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
-index dc7528b41aa9a55807a2b3e33d5668e1be681e79..a002218caed08bf4a4072d934e1094c22ee35534 100644
---- a/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
-+++ b/src/main/java/net/minecraft/util/CrudeIncrementalIntIdentityHashBiMap.java
-@@ -16,11 +16,13 @@ public class CrudeIncrementalIntIdentityHashBiMap<K> implements IdMap<K> {
- private K[] byId;
- private int nextId;
- private int size;
-+ private java.util.BitSet usedIds; // Paper
-
- private CrudeIncrementalIntIdentityHashBiMap(int size) {
- this.keys = (K[])(new Object[size]);
- this.values = new int[size];
- this.byId = (K[])(new Object[size]);
-+ this.usedIds = new java.util.BitSet(); // Paper
- }
-
- public static <A> CrudeIncrementalIntIdentityHashBiMap<A> create(int expectedSize) {
-@@ -57,9 +59,13 @@ public class CrudeIncrementalIntIdentityHashBiMap<K> implements IdMap<K> {
- }
-
- private int nextId() {
-+ /* // Paper start
- while(this.nextId < this.byId.length && this.byId[this.nextId] != null) {
- ++this.nextId;
- }
-+ */
-+ this.nextId = this.usedIds.nextClearBit(0);
-+ // Paper end
-
- return this.nextId;
- }
-@@ -80,6 +86,7 @@ public class CrudeIncrementalIntIdentityHashBiMap<K> implements IdMap<K> {
- this.byId = crudeIncrementalIntIdentityHashBiMap.byId;
- this.nextId = crudeIncrementalIntIdentityHashBiMap.nextId;
- this.size = crudeIncrementalIntIdentityHashBiMap.size;
-+ this.usedIds.clear(); // Paper
- }
-
- public void addMapping(K value, int id) {
-@@ -96,6 +103,7 @@ public class CrudeIncrementalIntIdentityHashBiMap<K> implements IdMap<K> {
- this.keys[k] = value;
- this.values[k] = id;
- this.byId[id] = value;
-+ this.usedIds.set(id); // Paper
- ++this.size;
- if (id == this.nextId) {
- ++this.nextId;
-@@ -157,6 +165,7 @@ public class CrudeIncrementalIntIdentityHashBiMap<K> implements IdMap<K> {
- Arrays.fill(this.byId, (Object)null);
- this.nextId = 0;
- this.size = 0;
-+ this.usedIds.clear(); // Paper
- }
-
- @Override
diff --git a/patches/removed/1.18/No longer needed/0312-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch b/patches/removed/1.18/No longer needed/0312-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch
deleted file mode 100644
index b5f853a89b..0000000000
--- a/patches/removed/1.18/No longer needed/0312-MC-145260-Fix-Whitelist-On-Off-inconsistency.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 2 Mar 2019 16:12:35 -0500
-Subject: [PATCH] MC-145260: Fix Whitelist On/Off inconsistency
-
-mojang stored whitelist state in 2 places (Whitelist Object, PlayerList)
-
-some things checked PlayerList, some checked object. This moves
-everything to the Whitelist object.
-
-https://github.com/PaperMC/Paper/issues/1880
-
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index f8827ca44a6e099d0a8a05265b01e6f5da3567e0..b2e418db3b8b1b88b92234a9fc913a09d1325793 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -64,6 +64,7 @@ import net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket;
- import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket;
- import net.minecraft.network.protocol.game.ClientboundUpdateTagsPacket;
- import net.minecraft.resources.ResourceKey;
-+import net.minecraft.server.MCUtil;
- import net.minecraft.server.MinecraftServer;
- import net.minecraft.server.PlayerAdvancements;
- import net.minecraft.server.ServerScoreboard;
-@@ -1005,9 +1006,9 @@ public abstract class PlayerList {
- }
- public boolean isWhitelisted(GameProfile gameprofile, org.bukkit.event.player.PlayerLoginEvent loginEvent) {
- boolean isOp = this.ops.contains(gameprofile);
-- boolean isWhitelisted = !this.doWhiteList || isOp || this.whitelist.contains(gameprofile);
-+ boolean isWhitelisted = !this.isUsingWhitelist() || isOp || this.whitelist.contains(gameprofile); // Paper - use isUsingWhitelist()
- final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event;
-- event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(net.minecraft.server.MCUtil.toBukkit(gameprofile), this.doWhiteList, isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage);
-+ event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(net.minecraft.server.MCUtil.toBukkit(gameprofile), this.isUsingWhitelist(), isWhitelisted, isOp, org.spigotmc.SpigotConfig.whitelistMessage); // Paper - use isUsingWhitelist()
- event.callEvent();
- if (!event.isWhitelisted()) {
- if (loginEvent != null) {
diff --git a/patches/removed/1.18/No longer needed/0341-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch b/patches/removed/1.18/No longer needed/0341-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch
deleted file mode 100644
index f7564920c4..0000000000
--- a/patches/removed/1.18/No longer needed/0341-Do-less-work-if-we-have-a-custom-Bukkit-generator.patch
+++ /dev/null
@@ -1,44 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Paul Sauve <[email protected]>
-Date: Sun, 14 Jul 2019 21:05:03 -0500
-Subject: [PATCH] Do less work if we have a custom Bukkit generator
-
-#NOTE: Doesn't look like this is needed anymore. See MinecraftServer#setInitialSpawn
-
-If the Bukkit generator already has a spawn, use it immediately instead
-of spending time generating one that we won't use
-
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index cae3c20eba546dcf42d035e9a5998302df350d4b..7ef0c9c8edf1202d0e20a505b18f9d36bdc20139 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -691,12 +691,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- worldProperties.setSpawn(BlockPos.ZERO.above(80), 0.0F);
- } else {
- ChunkGenerator chunkgenerator = world.getChunkSource().getGenerator();
-- BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource();
-- Random random = new Random(world.getSeed());
-- BlockPos blockposition = worldchunkmanager.findBiomeHorizontal(0, world.getSeaLevel(), 0, 256, (biomebase) -> {
-- return biomebase.getMobSettings().playerSpawnFriendly();
-- }, random);
-- ChunkPos chunkcoordintpair = blockposition == null ? new ChunkPos(0, 0) : new ChunkPos(blockposition);
-+ // Paper start - moved down
- // CraftBukkit start
- if (world.generator != null) {
- Random rand = new Random(world.getSeed());
-@@ -712,6 +707,15 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- }
- }
- // CraftBukkit end
-+ // Paper start - if the generator created a spawn for us, then there is no need for us to also create a spawn -
-+ // only do it if the generator did not
-+ BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource();
-+ Random random = new Random(world.getSeed());
-+ BlockPos blockposition = worldchunkmanager.findBiomeHorizontal(0, world.getSeaLevel(), 0, 256, (biomebase) -> {
-+ return biomebase.getMobSettings().playerSpawnFriendly();
-+ }, random);
-+ ChunkPos chunkcoordintpair = blockposition == null ? new ChunkPos(0, 0) : new ChunkPos(blockposition);
-+ // Paper end
-
- if (blockposition == null) {
- MinecraftServer.LOGGER.warn("Unable to find spawn biome");
diff --git a/patches/removed/1.18/No longer needed/0345-Fix-MC-161754.patch b/patches/removed/1.18/No longer needed/0345-Fix-MC-161754.patch
deleted file mode 100644
index 6cabe45c01..0000000000
--- a/patches/removed/1.18/No longer needed/0345-Fix-MC-161754.patch
+++ /dev/null
@@ -1,25 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Tue, 24 Sep 2019 16:03:00 -0700
-Subject: [PATCH] Fix MC-161754
-
-#NOTE: the mojira issue is marked as fixed in 20w12a
-
-Fixes https://github.com/PaperMC/Paper/issues/2580
-
-We can use an entity valid check since this method is invoked for
-each inventory iteraction (thanks to CB) and on player tick (vanilla).
-
-diff --git a/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java b/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java
-index f3e17f00f09feb4df4274b09bc7fa3c3022a5fbb..ffc596d8db77aaeed43f79b895bf4a1c7baeeab2 100644
---- a/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java
-+++ b/src/main/java/net/minecraft/world/inventory/HorseInventoryMenu.java
-@@ -95,7 +95,7 @@ public class HorseInventoryMenu extends AbstractContainerMenu {
-
- @Override
- public boolean stillValid(Player player) {
-- return !this.horse.hasInventoryChanged(this.horseContainer) && this.horseContainer.stillValid(player) && this.horse.isAlive() && this.horse.distanceTo((Entity) player) < 8.0F;
-+ return !this.horse.hasInventoryChanged(this.horseContainer) && this.horseContainer.stillValid(player) && (this.horse.isAlive() && this.horse.valid) && this.horse.distanceTo((Entity) player) < 8.0F; // Paper - Fix MC-161754
- }
-
- private boolean hasChest(AbstractHorse horse) {
diff --git a/patches/removed/1.18/No longer needed/0347-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch b/patches/removed/1.18/No longer needed/0347-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch
deleted file mode 100644
index b945946732..0000000000
--- a/patches/removed/1.18/No longer needed/0347-Fix-stuck-in-sneak-when-changing-worlds-MC-10657.patch
+++ /dev/null
@@ -1,34 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath <[email protected]>
-Date: Wed, 9 Oct 2019 21:51:43 -0500
-Subject: [PATCH] Fix stuck in sneak when changing worlds (MC-10657)
-
-#NOTE: Fixed in MC 1.14.2
-
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index a055419b381a1e244d9d88208f0fcf2e5ba6b379..3e3582742792858c2b8328676faed68ddb6da674 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -1097,6 +1097,8 @@ public class ServerPlayer extends Player {
- this.lastSentHealth = -1.0F;
- this.lastSentFood = -1;
-
-+ setShiftKeyDown(false); // Paper - fix MC-10657
-+
- // CraftBukkit start
- PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld());
- this.level.getCraftServer().getPluginManager().callEvent(changeEvent);
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index f01a416cbb0a7d786e5033d2a3b25cb7048c232b..a2eb7689eafe20db59357ab3fad0e59cdef3481a 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -829,6 +829,8 @@ public abstract class PlayerList {
- entityplayer.connection.send(new ClientboundUpdateMobEffectPacket(entityplayer.getId(), mobEffect));
- }
-
-+ entityplayer.setShiftKeyDown(false); // Paper - fix MC-10657
-+
- // Fire advancement trigger
- entityplayer.triggerDimensionChangeTriggers(((CraftWorld) fromWorld).getHandle());
-
diff --git a/patches/removed/1.18/No longer needed/0405-Remove-streams-from-PairedQueue.patch b/patches/removed/1.18/No longer needed/0405-Remove-streams-from-PairedQueue.patch
deleted file mode 100644
index 91f6f25ab6..0000000000
--- a/patches/removed/1.18/No longer needed/0405-Remove-streams-from-PairedQueue.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 6 Apr 2020 18:10:43 -0700
-Subject: [PATCH] Remove streams from PairedQueue
-
-We shouldn't be doing stream calls just to see if the queue is
-empty. This creates loads of garbage thanks to how often it's called.
-
-diff --git a/src/main/java/net/minecraft/util/thread/StrictQueue.java b/src/main/java/net/minecraft/util/thread/StrictQueue.java
-index 66591e23bc9e0df968fb6b291a3ad3773debdf29..c4a20df21e1fe5556fddac64b52d542579758e2c 100644
---- a/src/main/java/net/minecraft/util/thread/StrictQueue.java
-+++ b/src/main/java/net/minecraft/util/thread/StrictQueue.java
-@@ -22,9 +22,12 @@ public interface StrictQueue<T, F> {
- private final List<Queue<Runnable>> queueList;
-
- public FixedPriorityQueue(int priorityCount) {
-- this.queueList = IntStream.range(0, priorityCount).mapToObj((i) -> {
-- return Queues.newConcurrentLinkedQueue();
-- }).collect(Collectors.toList());
-+ // Paper start - remove streams
-+ this.queueList = new java.util.ArrayList<>(priorityCount); // queues
-+ for (int j = 0; j < priorityCount; ++j) {
-+ this.queueList.add(Queues.newConcurrentLinkedQueue());
-+ }
-+ // Paper end - remove streams
- }
-
- @Nullable
-@@ -49,7 +52,16 @@ public interface StrictQueue<T, F> {
-
- @Override
- public boolean isEmpty() {
-- return this.queueList.stream().allMatch(Collection::isEmpty);
-+ // Paper start - remove streams
-+ // why are we doing streams every time we might want to execute a task?
-+ for (int i = 0, len = this.queueList.size(); i < len; ++i) {
-+ Queue<Runnable> queue = this.queueList.get(i);
-+ if (!queue.isEmpty()) {
-+ return false;
-+ }
-+ }
-+ return true;
-+ // Paper end - remove streams
- }
-
- @Override
diff --git a/patches/removed/1.18/No longer needed/0410-Restrict-vanilla-teleport-command-to-valid-locations.patch b/patches/removed/1.18/No longer needed/0410-Restrict-vanilla-teleport-command-to-valid-locations.patch
deleted file mode 100644
index b0ce748234..0000000000
--- a/patches/removed/1.18/No longer needed/0410-Restrict-vanilla-teleport-command-to-valid-locations.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Zach Brown <[email protected]>
-Date: Thu, 16 Apr 2020 20:07:29 -0500
-Subject: [PATCH] Restrict vanilla teleport command to valid locations
-
-Fixes GH-3165, GH-3575
-
-diff --git a/src/main/java/net/minecraft/server/commands/TeleportCommand.java b/src/main/java/net/minecraft/server/commands/TeleportCommand.java
-index 85ae18b7f8a26f83ea0cf1ae17cfa88b796fcc77..d0109df7fad51fc0444459f5d367254c8f4c355e 100644
---- a/src/main/java/net/minecraft/server/commands/TeleportCommand.java
-+++ b/src/main/java/net/minecraft/server/commands/TeleportCommand.java
-@@ -148,6 +148,12 @@ public class TeleportCommand {
-
- private static void performTeleport(CommandSourceStack source, Entity target, ServerLevel world, double x, double y, double z, Set<ClientboundPlayerPositionPacket.RelativeArgument> movementFlags, float yaw, float pitch, @Nullable TeleportCommand.LookAt facingLocation) throws CommandSyntaxException {
- BlockPos blockposition = new BlockPos(x, y, z);
-+ // Paper start - Don't allow teleport command to invalid locations
-+ if (x <= -30000000 || z <= -30000000 || x > 30000000 || z > 30000000 || y > 30000000 || y <= -30000000) { // Copy/pasta from BaseBlockPosition#isValidLocation
-+ org.bukkit.Bukkit.getLogger().warning("Refused to teleport " + target.getScoreboardName() + " to " + x + ", " + y + ", " + z);
-+ return;
-+ }
-+ // Paper end
-
- if (!Level.isInSpawnableBounds(blockposition)) {
- throw TeleportCommand.INVALID_POSITION.create();
diff --git a/patches/removed/1.18/No longer needed/0432-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch b/patches/removed/1.18/No longer needed/0432-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch
deleted file mode 100644
index 0350128805..0000000000
--- a/patches/removed/1.18/No longer needed/0432-Fix-Non-Full-Status-Chunk-NBT-Memory-Leak.patch
+++ /dev/null
@@ -1,102 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 23 May 2020 01:31:06 -0400
-Subject: [PATCH] Fix Non Full Status Chunk NBT Memory Leak
-
-Any full status chunk that was requested for any status less than full
-would hold onto their entire nbt tree and every variable in that function.
-
-This was due to use of a lambda that persists on the Chunk object
-until that chunk reaches FULL status.
-
-With introduction of no tick, we greatly increased the number of non
-full chunks so this was really starting to hurt.
-
-We further improve it by making a copy of the nbt tag with only the memory
-it needs, so that we dont have to hold a copy to the entire compound.
-
-# 1.18: The postLoadChunk method was refactored to not keep a reference to the full nbt,
-just the two nbt tag lists it needed, "entities" and "block_entities"
-
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-index 79f2b3942a3ccccd8fe8719db12de458212e8659..d113b4835e86a789c0ba124eb839e1c56a5437d2 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-@@ -25,6 +25,7 @@ import net.minecraft.nbt.CompoundTag;
- import net.minecraft.nbt.ListTag;
- import net.minecraft.nbt.LongArrayTag;
- import net.minecraft.nbt.ShortTag;
-+import net.minecraft.nbt.Tag;
- import net.minecraft.server.level.ServerChunkCache;
- import net.minecraft.server.level.ServerLevel;
- import net.minecraft.server.level.ThreadedLevelLightEngine;
-@@ -205,15 +206,9 @@ public class ChunkSerializer {
- object2 = protochunkticklist1;
- }
-
-- object = new LevelChunk(world.getLevel(), pos, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, k, achunksection, (chunk) -> {
-- ChunkSerializer.postLoadChunk(world, nbttagcompound1, chunk);
-- // CraftBukkit start - load chunk persistent data from nbt
-- net.minecraft.nbt.Tag persistentBase = nbttagcompound1.get("ChunkBukkitValues");
-- if (persistentBase instanceof CompoundTag) {
-- chunk.persistentDataContainer.putAll((CompoundTag) persistentBase);
-- }
-- // CraftBukkit end
-- });
-+ object = new LevelChunk(world.getLevel(), pos, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, k, achunksection, // Paper start - fix massive nbt memory leak due to lambda. move lambda into a container method to not leak scope. Only clone needed NBT keys.
-+ createLoadEntitiesConsumer(new SafeNBTCopy(nbttagcompound1, "TileEntities", "Entities", "ChunkBukkitValues")) // Paper - move CB Chunk PDC into here
-+ ); // Paper end
- } else {
- ProtoChunk protochunk = new ProtoChunk(pos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world, world); // Paper - add level
-
-@@ -319,6 +314,50 @@ public class ChunkSerializer {
- return new InProgressChunkHolder(protochunk1, tasksToExecuteOnMain); // Paper - Async chunk loading
- }
- }
-+ // Paper start
-+
-+ /**
-+ * This wrapper will error out if any key is accessed that wasn't copied so we can catch it easy on an update
-+ */
-+ private static class SafeNBTCopy extends CompoundTag {
-+ private final java.util.Set<String> keys = new java.util.HashSet<String>();
-+ public SafeNBTCopy(CompoundTag base, String... keys) {
-+ for (String key : keys) {
-+ this.keys.add(key);
-+ final Tag nbtBase = base.get(key);
-+ if (nbtBase != null) {
-+ this.put(key, nbtBase);
-+ }
-+ }
-+ }
-+
-+ @Override
-+ public boolean contains(String key) {
-+ if (super.contains(key)) {
-+ return true;
-+ } else if (keys.contains(key)) {
-+ return false;
-+ }
-+ throw new IllegalStateException("Missing Key " + key + " in SafeNBTCopy");
-+ }
-+
-+ @Override
-+ public boolean contains(String key, int type) {
-+ return contains(key) && super.contains(key, type);
-+ }
-+ }
-+ private static java.util.function.Consumer<LevelChunk> createLoadEntitiesConsumer(CompoundTag nbt) {
-+ return (chunk) -> {
-+ postLoadChunk(chunk.level, nbt, chunk);
-+ // CraftBukkit start - load chunk persistent data from nbt
-+ Tag persistentBase = nbt.get("ChunkBukkitValues");
-+ if (persistentBase instanceof CompoundTag) {
-+ chunk.persistentDataContainer.putAll((CompoundTag) persistentBase);
-+ }
-+ // CraftBukkit end
-+ };
-+ }
-+ // Paper end
-
- // Paper start - async chunk save for unload
- public static final class AsyncSaveData {
diff --git a/patches/removed/1.18/No longer needed/0463-Optimize-NibbleArray-to-use-pooled-buffers.patch b/patches/removed/1.18/No longer needed/0463-Optimize-NibbleArray-to-use-pooled-buffers.patch
deleted file mode 100644
index bff1c4bc34..0000000000
--- a/patches/removed/1.18/No longer needed/0463-Optimize-NibbleArray-to-use-pooled-buffers.patch
+++ /dev/null
@@ -1,378 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 6 May 2020 23:30:30 -0400
-Subject: [PATCH] Optimize NibbleArray to use pooled buffers
-
-Massively reduces memory allocation of 2048 byte buffers by using
-an object pool for these.
-
-Uses lots of advanced new capabilities of the Paper codebase :)
-
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-index 7825d6f0fdcfda6212cff8033ec55fb7db236154..2218ddb8d075d042bb7c41886aa9dd2082a5a40f 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket.java
-@@ -2,8 +2,11 @@ package net.minecraft.network.protocol.game;
-
- import java.util.BitSet;
- import javax.annotation.Nullable;
-+
-+import io.netty.channel.ChannelFuture;
- import net.minecraft.network.FriendlyByteBuf;
- import net.minecraft.network.protocol.Packet;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.world.level.ChunkPos;
- import net.minecraft.world.level.chunk.LevelChunk;
- import net.minecraft.world.level.lighting.LevelLightEngine;
-@@ -14,6 +17,23 @@ public class ClientboundLevelChunkWithLightPacket implements Packet<ClientGamePa
- private final ClientboundLevelChunkPacketData chunkData;
- private final ClientboundLightUpdatePacketData lightData;
-
-+ // Paper start
-+ @Override
-+ public void onPacketDispatch(ServerPlayer player) {
-+ lightData.onPacketDispatch(player);
-+ }
-+
-+ @Override
-+ public void onPacketDispatchFinish(ServerPlayer player, ChannelFuture future) {
-+ lightData.onPacketDispatchFinish(player, future);
-+ }
-+
-+ @Override
-+ public boolean hasFinishListener() {
-+ return true;
-+ }
-+ // Paper end
-+
- public ClientboundLevelChunkWithLightPacket(LevelChunk chunk, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits, boolean nonEdge) {
- ChunkPos chunkPos = chunk.getPos();
- this.x = chunkPos.x;
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java
-index 15350c301ba670cd86c83c7051c3571ff2759d8f..da46695e4d45f701a216767a048b21e289f056f6 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java
-@@ -2,8 +2,11 @@ package net.minecraft.network.protocol.game;
-
- import java.util.BitSet;
- import javax.annotation.Nullable;
-+
-+import io.netty.channel.ChannelFuture;
- import net.minecraft.network.FriendlyByteBuf;
- import net.minecraft.network.protocol.Packet;
-+import net.minecraft.server.level.ServerPlayer;
- import net.minecraft.world.level.ChunkPos;
- import net.minecraft.world.level.lighting.LevelLightEngine;
-
-@@ -12,6 +15,23 @@ public class ClientboundLightUpdatePacket implements Packet<ClientGamePacketList
- private final int z;
- private final ClientboundLightUpdatePacketData lightData;
-
-+ // Paper start
-+ @Override
-+ public void onPacketDispatch(ServerPlayer player) {
-+ lightData.onPacketDispatch(player);
-+ }
-+
-+ @Override
-+ public void onPacketDispatchFinish(ServerPlayer player, ChannelFuture future) {
-+ lightData.onPacketDispatchFinish(player, future);
-+ }
-+
-+ @Override
-+ public boolean hasFinishListener() {
-+ return true;
-+ }
-+ // Paper end
-+
- public ClientboundLightUpdatePacket(ChunkPos chunkPos, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits, boolean nonEdge) {
- this.x = chunkPos.x;
- this.z = chunkPos.z;
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacketData.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacketData.java
-index fe9cfb2c6e3ecbe8966bc33a16785f03f870e7cf..e887317e8fcf71740ec96d85b7ea5b819a39d468 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacketData.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacketData.java
-@@ -19,6 +19,27 @@ public class ClientboundLightUpdatePacketData {
- private final List<byte[]> skyUpdates;
- private final List<byte[]> blockUpdates;
- private final boolean trustEdges;
-+ // Paper start
-+ java.lang.Runnable cleaner1;
-+ java.lang.Runnable cleaner2;
-+ java.util.concurrent.atomic.AtomicInteger remainingSends = new java.util.concurrent.atomic.AtomicInteger(0);
-+
-+ public void onPacketDispatch(net.minecraft.server.level.ServerPlayer player) {
-+ remainingSends.incrementAndGet();
-+ }
-+
-+ public void onPacketDispatchFinish(net.minecraft.server.level.ServerPlayer player, io.netty.channel.ChannelFuture future) {
-+ if (remainingSends.decrementAndGet() <= 0) {
-+ // incase of any race conditions, schedule this delayed
-+ net.minecraft.server.MCUtil.scheduleTask(5, () -> {
-+ if (remainingSends.get() == 0) {
-+ cleaner1.run();
-+ cleaner2.run();
-+ }
-+ }, "Light Packet Release");
-+ }
-+ }
-+ // Paper end
-
- public ClientboundLightUpdatePacketData(ChunkPos pos, LevelLightEngine lightProvider, @Nullable BitSet skyBits, @Nullable BitSet blockBits, boolean nonEdge) {
- this.trustEdges = nonEdge;
-@@ -26,8 +47,8 @@ public class ClientboundLightUpdatePacketData {
- this.blockYMask = new BitSet();
- this.emptySkyYMask = new BitSet();
- this.emptyBlockYMask = new BitSet();
-- this.skyUpdates = Lists.newArrayList();
-- this.blockUpdates = Lists.newArrayList();
-+ this.skyUpdates = Lists.newArrayList();this.cleaner1 = net.minecraft.server.MCUtil.registerListCleaner(this, this.skyUpdates, DataLayer::releaseBytes); // Paper
-+ this.blockUpdates = Lists.newArrayList();this.cleaner2 = net.minecraft.server.MCUtil.registerListCleaner(this, this.blockUpdates, DataLayer::releaseBytes); // Paper
-
- for(int i = 0; i < lightProvider.getLightSectionCount(); ++i) {
- if (skyBits == null || skyBits.get(i)) {
-@@ -72,7 +93,7 @@ public class ClientboundLightUpdatePacketData {
- uninitialized.set(y);
- } else {
- initialized.set(y);
-- nibbles.add((byte[])dataLayer.getData().clone());
-+ nibbles.add((byte[])dataLayer.getCloneIfSet()); // Paper
- }
- }
-
-diff --git a/src/main/java/net/minecraft/world/level/chunk/DataLayer.java b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java
-index 81701abd11fbc4671393a76a42973f53835ca234..e8cf0088e94925934acd02ba05b9411bd7cf186e 100644
---- a/src/main/java/net/minecraft/world/level/chunk/DataLayer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java
-@@ -13,11 +13,65 @@ public final class DataLayer {
- private static final int NIBBLE_SIZE = 4;
- @Nullable
- protected byte[] data;
-+ // Paper start
-+ public static byte[] EMPTY_NIBBLE = new byte[2048];
-+ private static final int nibbleBucketSizeMultiplier = Integer.getInteger("Paper.nibbleBucketSize", 3072);
-+ private static final int maxPoolSize = Integer.getInteger("Paper.maxNibblePoolSize", (int) Math.min(6, Math.max(1, Runtime.getRuntime().maxMemory() / 1024 / 1024 / 1024)) * (nibbleBucketSizeMultiplier * 8));
-+ public static final com.destroystokyo.paper.util.pooled.PooledObjects<byte[]> BYTE_2048 = new com.destroystokyo.paper.util.pooled.PooledObjects<>(() -> new byte[2048], maxPoolSize);
-+ public static void releaseBytes(byte[] bytes) {
-+ if (bytes != null && bytes != EMPTY_NIBBLE && bytes.length == 2048) {
-+ System.arraycopy(EMPTY_NIBBLE, 0, bytes, 0, 2048);
-+ BYTE_2048.release(bytes);
-+ }
-+ }
-
-+ public DataLayer markPoolSafe(byte[] bytes) {
-+ if (bytes != EMPTY_NIBBLE) this.data = bytes;
-+ return markPoolSafe();
-+ }
-+ public DataLayer markPoolSafe() {
-+ poolSafe = true;
-+ return this;
-+ }
-+ public byte[] getIfSet() {
-+ return this.data != null ? this.data : EMPTY_NIBBLE;
-+ }
-+ public byte[] getCloneIfSet() {
-+ if (data == null) {
-+ return EMPTY_NIBBLE;
-+ }
-+ byte[] ret = BYTE_2048.acquire();
-+ System.arraycopy(getIfSet(), 0, ret, 0, 2048);
-+ return ret;
-+ }
-+
-+ public DataLayer cloneAndSet(byte[] bytes) {
-+ if (bytes != null && bytes != EMPTY_NIBBLE) {
-+ this.data = BYTE_2048.acquire();
-+ System.arraycopy(bytes, 0, this.data, 0, 2048);
-+ }
-+ return this;
-+ }
-+ boolean poolSafe = false;
-+ public java.lang.Runnable cleaner;
-+ private void registerCleaner() {
-+ if (!poolSafe) {
-+ cleaner = net.minecraft.server.MCUtil.registerCleaner(this, this.data, DataLayer::releaseBytes);
-+ } else {
-+ cleaner = net.minecraft.server.MCUtil.once(() -> DataLayer.releaseBytes(this.data));
-+ }
-+ }
- public DataLayer() {}
-
- public DataLayer(byte[] bytes) {
-+ // Paper start
-+ this(bytes, false);
-+ }
-+ public DataLayer(byte[] bytes, boolean isSafe) {
- this.data = bytes;
-+ if (!isSafe) this.data = getCloneIfSet(); // Paper - clone for safety
-+ registerCleaner();
-+ // Paper end
- if (bytes.length != 2048) {
- throw (IllegalArgumentException) Util.pauseInIde(new IllegalArgumentException("DataLayer should be 2048 bytes not: " + bytes.length));
- }
-@@ -52,7 +106,8 @@ public final class DataLayer {
-
- private void set(int index, int value) {
- if (this.data == null) {
-- this.data = new byte[2048];
-+ this.data = BYTE_2048.acquire(); // Paper
-+ registerCleaner(); // Paper
- }
-
- int k = DataLayer.getByteIndex(index);
-@@ -74,13 +129,33 @@ public final class DataLayer {
- public byte[] getData() {
- if (this.data == null) {
- this.data = new byte[2048];
-+ } else { // Paper start
-+ // Accessor may need this object past garbage collection so need to clone it and return pooled value
-+ // If we know its safe for pre GC access, use asBytesPoolSafe(). If you just need read, use getIfSet()
-+ Runnable cleaner = this.cleaner;
-+ if (cleaner != null) {
-+ this.data = this.data.clone();
-+ cleaner.run(); // release the previously pooled value
-+ this.cleaner = null;
-+ }
- }
-+ // Paper end
-
- return this.data;
- }
-
-+ @javax.annotation.Nonnull
-+ public byte[] asBytesPoolSafe() {
-+ if (this.data == null) {
-+ this.data = BYTE_2048.acquire(); // Paper
-+ registerCleaner(); // Paper
-+ }
-+
-+ return this.data;
-+ }
-+ // Paper end
- public DataLayer copy() {
-- return this.data == null ? new DataLayer() : new DataLayer((byte[]) this.data.clone());
-+ return this.data == null ? new DataLayer() : new DataLayer(this.data); // Paper - clone in ctor
- }
-
- public String toString() {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-index 980c784b8e5365b62cbeef7f32af9f4383cc01e6..46beea026eec707c69194da6d1d51dc66b61f54e 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-@@ -502,11 +502,11 @@ public class ChunkSerializer {
- }
-
- if (nibblearray != null && !nibblearray.isEmpty()) {
-- nbttagcompound1.putByteArray("BlockLight", nibblearray.getData());
-+ nbttagcompound1.putByteArray("BlockLight", nibblearray.asBytesPoolSafe().clone()); // Paper
- }
-
- if (nibblearray1 != null && !nibblearray1.isEmpty()) {
-- nbttagcompound1.putByteArray("SkyLight", nibblearray1.getData());
-+ nbttagcompound1.putByteArray("SkyLight", nibblearray1.asBytesPoolSafe().clone()); // Paper
- }
-
- if (!nbttagcompound1.isEmpty()) {
-diff --git a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java
-index f357a3473682c2d37a20fb862522c67b9979402a..52682471adc13dffc0383fc4abacbd3397f3bb10 100644
---- a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java
-+++ b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java
-@@ -34,7 +34,9 @@ public abstract class DataLayerStorageMap<M extends DataLayerStorageMap<M>> {
-
- public void copyDataLayer(long pos) {
- if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data
-- this.data.queueUpdate(pos, ((DataLayer) this.data.getUpdating(pos)).copy()); // Paper - avoid copying light data
-+ DataLayer updating = this.data.getUpdating(pos); // Paper - pool nibbles
-+ this.data.queueUpdate(pos, new DataLayer().markPoolSafe(updating.getCloneIfSet())); // Paper - avoid copying light data - pool safe clone
-+ if (updating.cleaner != null) net.minecraft.server.MCUtil.scheduleTask(2, updating.cleaner, "Light Engine Release"); // Paper - delay clean incase anything holding ref was still using it
- this.clearCache();
- }
-
-diff --git a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java
-index 4f7b63f2cc8a69fa8efb3a84f6abc3d3dcf05b49..cae559b37b5404851fa99d1d206232b5e7ab770c 100644
---- a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java
-@@ -157,7 +157,7 @@ public abstract class LayerLightSectionStorage<M extends DataLayerStorageMap<M>>
-
- protected DataLayer createDataLayer(long sectionPos) {
- DataLayer dataLayer = this.queuedSections.get(sectionPos);
-- return dataLayer != null ? dataLayer : new DataLayer();
-+ return dataLayer != null ? dataLayer : new DataLayer().markPoolSafe(); // Paper
- }
-
- protected void clearQueuedSectionBlocks(LayerLightEngine<?, ?> storage, long sectionPos) {
-@@ -318,12 +318,12 @@ public abstract class LayerLightSectionStorage<M extends DataLayerStorageMap<M>>
-
- protected void queueSectionData(long sectionPos, @Nullable DataLayer array, boolean nonEdge) {
- if (array != null) {
-- this.queuedSections.put(sectionPos, array);
-+ DataLayer remove = this.queuedSections.put(sectionPos, array); if (remove != null && remove.cleaner != null) remove.cleaner.run(); // Paper - clean up when removed
- if (!nonEdge) {
- this.untrustedSections.add(sectionPos);
- }
- } else {
-- this.queuedSections.remove(sectionPos);
-+ DataLayer remove = this.queuedSections.remove(sectionPos); if (remove != null && remove.cleaner != null) remove.cleaner.run(); // Paper - clean up when removed
- }
-
- }
-diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java
-index 9797254e981d08d3934f7ca8f369dd78a6ef1a48..4012d87dc27c3b1096fdaa60bfdfd68f27a22da7 100644
---- a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java
-@@ -163,14 +163,14 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
-
- return repeatFirstLayer(dataLayer2);
- } else {
-- return new DataLayer();
-+ return new DataLayer().markPoolSafe(); // Paper - mark pool use as safe (no auto cleaner)
- }
- }
- }
-
- private static DataLayer repeatFirstLayer(DataLayer source) {
- if (source.isEmpty()) {
-- return new DataLayer();
-+ return new DataLayer().markPoolSafe(); // Paper - mark pool use as safe (no auto cleaner)
- } else {
- byte[] bs = source.getData();
- byte[] cs = new byte[2048];
-@@ -179,7 +179,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
- System.arraycopy(bs, 0, cs, i * 128, 128);
- }
-
-- return new DataLayer(cs);
-+ return new DataLayer(cs).markPoolSafe(cs); // Paper - mark pool use as safe (no auto cleaner)
- }
- }
-
-@@ -197,7 +197,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
- this.updatingSectionData.copyDataLayer(l);
- }
-
-- Arrays.fill(this.getDataLayer(l, true).getData(), (byte)-1);
-+ Arrays.fill(this.getDataLayer(l, true).asBytesPoolSafe(), (byte)-1); // Paper
- int j = SectionPos.sectionToBlockCoord(SectionPos.x(l));
- int k = SectionPos.sectionToBlockCoord(SectionPos.y(l));
- int m = SectionPos.sectionToBlockCoord(SectionPos.z(l));
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-index 7bc1219523eeb0880493e6fb42692f1fdb30c110..187366c33c86b220581c3deac9168d6b6a2c5a3e 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-@@ -339,14 +339,14 @@ public class CraftChunk implements Chunk {
- sectionSkyLights[i] = CraftChunk.emptyLight;
- } else {
- sectionSkyLights[i] = new byte[2048];
-- System.arraycopy(skyLightArray.getData(), 0, sectionSkyLights[i], 0, 2048);
-+ System.arraycopy(skyLightArray.getIfSet(), 0, sectionSkyLights[i], 0, 2048); // Paper
- }
- DataLayer emitLightArray = lightengine.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(x, i, z));
- if (emitLightArray == null) {
- sectionEmitLights[i] = CraftChunk.emptyLight;
- } else {
- sectionEmitLights[i] = new byte[2048];
-- System.arraycopy(emitLightArray.getData(), 0, sectionEmitLights[i], 0, 2048);
-+ System.arraycopy(emitLightArray.getIfSet(), 0, sectionEmitLights[i], 0, 2048); // Paper
- }
-
- if (biome != null) {
diff --git a/patches/removed/1.18/No longer needed/0501-Fix-MC-187716-Use-configured-height.patch b/patches/removed/1.18/No longer needed/0501-Fix-MC-187716-Use-configured-height.patch
deleted file mode 100644
index 17c3ae1ba3..0000000000
--- a/patches/removed/1.18/No longer needed/0501-Fix-MC-187716-Use-configured-height.patch
+++ /dev/null
@@ -1,45 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: William Blake Galbreath <[email protected]>
-Date: Sat, 15 Aug 2020 08:04:49 -0500
-Subject: [PATCH] Fix MC-187716 Use configured height
-
-
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherCappedSurfaceBuilder.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherCappedSurfaceBuilder.java
-index 5ad6e0ef718a1775a2310925b8273120687230b1..cdfbdb411eacf5f8c6ced2dbc5789e0d49b93e1f 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherCappedSurfaceBuilder.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherCappedSurfaceBuilder.java
-@@ -42,7 +42,7 @@ public abstract class NetherCappedSurfaceBuilder extends SurfaceBuilder<SurfaceB
- BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
- BlockState blockState3 = chunk.getBlockState(mutableBlockPos.set(k, 128, m));
-
-- for(int p = 127; p >= i; --p) {
-+ for(int p = height; p >= i; --p) { // Paper - fix MC-187716 - use configured height
- mutableBlockPos.set(k, p, m);
- BlockState blockState4 = chunk.getBlockState(mutableBlockPos);
- if (blockState3.is(defaultBlock.getBlock()) && (blockState4.isAir() || blockState4 == defaultFluid)) {
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherForestSurfaceBuilder.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherForestSurfaceBuilder.java
-index fbb756504609285d96e01c4079be06561f75331e..aa6be90ac272c7e1186d177768230b7f52371e68 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherForestSurfaceBuilder.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherForestSurfaceBuilder.java
-@@ -34,7 +34,7 @@ public class NetherForestSurfaceBuilder extends SurfaceBuilder<SurfaceBuilderBas
- int o = -1;
- BlockState blockState = surfaceBuilderBaseConfiguration.getUnderMaterial();
-
-- for(int p = 127; p >= i; --p) {
-+ for(int p = height; p >= i; --p) { // Paper - fix MC-187716 - use configured height
- mutableBlockPos.set(k, p, m);
- BlockState blockState2 = surfaceBuilderBaseConfiguration.getTopMaterial();
- BlockState blockState3 = chunk.getBlockState(mutableBlockPos);
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherSurfaceBuilder.java b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherSurfaceBuilder.java
-index 0cdb3a1ca76375fc69d1709cdd34d4176a99a617..206f74305a01604892ff98ece0c8344cc5582d14 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherSurfaceBuilder.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/surfacebuilders/NetherSurfaceBuilder.java
-@@ -36,7 +36,7 @@ public class NetherSurfaceBuilder extends SurfaceBuilder<SurfaceBuilderBaseConfi
- BlockState blockState = surfaceBuilderBaseConfiguration.getTopMaterial();
- BlockState blockState2 = surfaceBuilderBaseConfiguration.getUnderMaterial();
-
-- for(int p = 127; p >= i; --p) {
-+ for(int p = height; p >= i; --p) { // Paper - fix MC-187716 - use configured height
- mutableBlockPos.set(k, p, m);
- BlockState blockState3 = chunk.getBlockState(mutableBlockPos);
- if (blockState3.isAir()) {
diff --git a/patches/removed/1.18/No longer needed/0525-Seed-based-feature-search.patch b/patches/removed/1.18/No longer needed/0525-Seed-based-feature-search.patch
deleted file mode 100644
index adf205fc13..0000000000
--- a/patches/removed/1.18/No longer needed/0525-Seed-based-feature-search.patch
+++ /dev/null
@@ -1,71 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Phoenix616 <[email protected]>
-Date: Mon, 13 Jan 2020 15:40:32 +0100
-Subject: [PATCH] Seed based feature search
-
-This tries to work around the issue where the server will load
-surrounding chunks up to a radius of 100 chunks in order to search for
-features e.g. when running the /locate command or for treasure maps
-(issue #2312).
-This is done by backporting Mojang's change in 1.17 which makes it so
-that the biome (generated by the seed) is checked first if the feature
-can be generated before actually to load the chunk.
-
-Additionally to that the center location of the target chunk is simply
-returned if the chunk is not loaded to avoid the sync chunk load.
-As this can lead to less precise locations a toggle is provided to
-enable the sync loading of the target chunk again.
-
-The main downside of this is that it breaks once the seed or generator
-changes but this should usually not happen. A config option to disable
-this completely is added though in case that should ever be necessary.
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index d985472c7982130cfa3a7d85694fd692717fa295..52c2373e1277e3f98bdfeafae0ef79158622b753 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -400,6 +400,14 @@ public class PaperWorldConfig {
- }
- }
-
-+ public boolean seedBasedFeatureSearch = true;
-+ public boolean seedBasedFeatureSearchLoadsChunks = true;
-+ private void seedBasedFeatureSearch() {
-+ seedBasedFeatureSearch = getBoolean("seed-based-feature-search", seedBasedFeatureSearch);
-+ seedBasedFeatureSearchLoadsChunks = getBoolean("seed-based-feature-search-loads-chunks", seedBasedFeatureSearchLoadsChunks);
-+ log("Feature search is based on seed: " + seedBasedFeatureSearch + ", loads chunks:" + seedBasedFeatureSearchLoadsChunks);
-+ }
-+
- public int maxCollisionsPerEntity;
- private void maxEntityCollision() {
- maxCollisionsPerEntity = getInt( "max-entity-collisions", this.spigotConfig.getInt("max-entity-collisions", 8) );
-diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java
-index 0670c1f72d9d0c4f8ea32ed314f75a106b2e2b09..6389e94abf3bae3e3cbeac11e0c99b1e50964563 100644
---- a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java
-+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java
-@@ -175,7 +175,24 @@ public class StructureFeature<C extends FeatureConfiguration> {
- return this.getLocatePos(chunkPos);
- }
-
-- ChunkAccess chunkAccess = levelReader.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_STARTS);
-+ // Paper start - seed based feature search
-+ ChunkAccess chunkAccess = null;
-+ if (structureAccessor.getWorld().paperConfig.seedBasedFeatureSearch) {
-+ Biome biomeBase = structureAccessor.getWorld().getBiomeManager().getBiome(new BlockPos(chunkPos.getMinBlockX() + 9, 0, chunkPos.getMinBlockZ() + 9));
-+ if (!biomeBase.getGenerationSettings().isValidStart(this)) {
-+ continue;
-+ }
-+ if (!structureAccessor.getWorld().paperConfig.seedBasedFeatureSearchLoadsChunks) {
-+ chunkAccess = structureAccessor.getWorld().getChunkIfLoaded(chunkPos.x, chunkPos.z);
-+ if (chunkAccess == null) {
-+ return chunkPos.getWorldPosition().offset(8, searchStartPos.getY(), 8);
-+ }
-+ }
-+ }
-+ if (chunkAccess == null) {
-+ chunkAccess = levelReader.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_STARTS);
-+ }
-+ // Paper end
- StructureStart<?> structureStart = structureAccessor.getStartForFeature(SectionPos.bottomOf(chunkAccess), this, chunkAccess);
- if (structureStart != null && structureStart.isValid()) {
- if (skipExistingChunks && structureStart.canBeReferenced()) {
diff --git a/patches/removed/1.18/No longer needed/0626-MC-29274-Fix-Wither-hostility-towards-players.patch b/patches/removed/1.18/No longer needed/0626-MC-29274-Fix-Wither-hostility-towards-players.patch
deleted file mode 100644
index bae1c53a29..0000000000
--- a/patches/removed/1.18/No longer needed/0626-MC-29274-Fix-Wither-hostility-towards-players.patch
+++ /dev/null
@@ -1,34 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: TheShermanTanker <[email protected]>
-Date: Thu, 1 Oct 2020 01:11:03 +0800
-Subject: [PATCH] MC-29274: Fix Wither hostility towards players
-
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index 843878bb83dfebca8d3574c159c5e338f15104ac..dfd4aac7a6e184a79bfdf15d9b5a92dbcb06fadd 100644
---- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-@@ -812,5 +812,11 @@ public class PaperWorldConfig {
- private void setUpdatePathfindingOnBlockUpdate() {
- updatePathfindingOnBlockUpdate = getBoolean("update-pathfinding-on-block-update", this.updatePathfindingOnBlockUpdate);
- }
-+
-+ public boolean fixWitherTargetingBug = false;
-+ private void witherSettings() {
-+ fixWitherTargetingBug = getBoolean("fix-wither-targeting-bug", false);
-+ log("Withers properly target players: " + fixWitherTargetingBug);
-+ }
- }
-
-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 c4b075fffae1e535b887740d48bd26da223d6798..9a5294596eb50cf4f6cf0729f6b94faaa63a5871 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
-@@ -105,6 +105,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
- this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 8.0F));
- this.goalSelector.addGoal(7, new RandomLookAroundGoal(this));
- this.targetSelector.addGoal(1, new HurtByTargetGoal(this, new Class[0]));
-+ if(this.level.paperConfig.fixWitherTargetingBug) this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, 0, false, false, null)); // Paper - Fix MC-29274
- this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Mob.class, 0, false, false, WitherBoss.LIVING_ENTITY_SELECTOR));
- }
-
diff --git a/patches/removed/1.18/No longer needed/0705-Fix-incorrect-status-dataconverter-for-pre-1.13-chun.patch b/patches/removed/1.18/No longer needed/0705-Fix-incorrect-status-dataconverter-for-pre-1.13-chun.patch
deleted file mode 100644
index 441425843a..0000000000
--- a/patches/removed/1.18/No longer needed/0705-Fix-incorrect-status-dataconverter-for-pre-1.13-chun.patch
+++ /dev/null
@@ -1,74 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 10 May 2021 15:46:57 -0700
-Subject: [PATCH] Fix incorrect status dataconverter for pre 1.13 chunks
-
-Vanilla was setting non-populated OR non-lit chunks to empty, but
-really this is just completely wrong. It should be set to "carved"
-at minmum, because pre 1.13 chunks went through 3 distinct stages
-of generation: carving, population, and lighting - in this order.
-There is no "empty" status, because a chunk was simply carved
-or it didn't exist. So mapping any chunk data to empty is simply
-invalid.
-
-If the chunk is terrain populated, then obviously it must be at
-minmum "decorated." If the chunk is lit and populated, then it is marked
-"mobs_spawned" (which is what Vanilla is doing, and this is the last
-stage before moving to full so it looks correct).
-
-So now here is a table representing the new status conversion:
-
-Chunk is lit Chunk is populated Vanilla
- F F empty
- T F empty
- F T empty
- T T mobs_spawned
-
-Chunk is lit Chunk is populated Paper
- F F carved
- T F carved
- F T decorated
- T T mobs_spawned
-
-This should fix some problems converting old data, as the
-changes here are going to prevent the chunk from being regenerated
-incorrectly.
-
-# 1.18: No longer needed, DataConverter handles this
-
-diff --git a/src/main/java/net/minecraft/util/datafix/fixes/ChunkToProtochunkFix.java b/src/main/java/net/minecraft/util/datafix/fixes/ChunkToProtochunkFix.java
-index 081bcae48ae34d8354635ea57952f09f14f7fa7a..a4305f58f793e1577de5e13132381ce81304cae4 100644
---- a/src/main/java/net/minecraft/util/datafix/fixes/ChunkToProtochunkFix.java
-+++ b/src/main/java/net/minecraft/util/datafix/fixes/ChunkToProtochunkFix.java
-@@ -36,17 +36,26 @@ public class ChunkToProtochunkFix extends DataFix {
- OpticFinder<?> opticFinder2 = DSL.fieldFinder("TileTicks", type5);
- return TypeRewriteRule.seq(this.fixTypeEverywhereTyped("ChunkToProtoChunkFix", type, this.getOutputSchema().getType(References.CHUNK), (typed) -> {
- return typed.updateTyped(opticFinder, type4, (typedx) -> {
-- Optional<? extends Stream<? extends Dynamic<?>>> optional = typedx.getOptionalTyped(opticFinder2).flatMap((typed) -> {
-- return typed.write().result();
-+ Optional<? extends Stream<? extends Dynamic<?>>> optional = typedx.getOptionalTyped(opticFinder2).flatMap((it) -> { // Paper - remap fix
-+ return it.write().result(); // Paper - remap fix
- }).flatMap((dynamicx) -> {
- return dynamicx.asStreamOpt().result();
- });
- Dynamic<?> dynamic = typedx.get(DSL.remainderFinder());
-- boolean bl = dynamic.get("TerrainPopulated").asBoolean(false) && (!dynamic.get("LightPopulated").asNumber().result().isPresent() || dynamic.get("LightPopulated").asBoolean(false));
-- dynamic = dynamic.set("Status", dynamic.createString(bl ? "mobs_spawned" : "empty"));
-+ // Paper start - fix incorrect status conversion
-+ // Vanilla is setting chunks to incorrect status here, they should be using at minimum carved.
-+ // for populated chunks, it should be at minimum decorated
-+ // and for lit and populated, mobs_spawned is correct (technically mobs_spawned should be for populated,
-+ // but if it's not lit then it can't be set above lit)
-+ final boolean terrainPopulated = dynamic.get("TerrainPopulated").asBoolean(false);
-+ final boolean lightPopulated = dynamic.get("LightPopulated").asBoolean(false) || dynamic.get("LightPopulated").asNumber().result().isPresent();
-+ final String newStatus = !terrainPopulated ? "carved" : (lightPopulated ? "mobs_spawned" : "decorated");
-+
-+ dynamic = dynamic.set("Status", dynamic.createString(newStatus));
-+ // Paper end - fix incorrect status conversion
- dynamic = dynamic.set("hasLegacyStructureData", dynamic.createBoolean(true));
- Dynamic<?> dynamic3;
-- if (bl) {
-+ if (true) { // Paper - fix incorrect status conversion
- Optional<ByteBuffer> optional2 = dynamic.get("Biomes").asByteBufferOpt().result();
- if (optional2.isPresent()) {
- ByteBuffer byteBuffer = optional2.get();
diff --git a/patches/removed/1.18/No longer needed/0758-Fix-and-optimize-legacy-world-conversion.patch b/patches/removed/1.18/No longer needed/0758-Fix-and-optimize-legacy-world-conversion.patch
deleted file mode 100644
index 278dc05bed..0000000000
--- a/patches/removed/1.18/No longer needed/0758-Fix-and-optimize-legacy-world-conversion.patch
+++ /dev/null
@@ -1,180 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jason Penilla <[email protected]>
-Date: Sun, 22 Aug 2021 23:03:33 -0700
-Subject: [PATCH] Fix and optimize legacy world conversion
-
-CraftBukkit breaks legacy world conversion in three ways:
-- Writes userdata to the path of the userdata folder rather than to
- the correct file inside the aforementioned folder. This causes the
- userdata folder to fail to be created as a file already exists at
- its path.
-- Makes changes to how multiworld works, without modifying
- McRegionUpgrader to be aware of these changes.
-- Calls methods on Bukkit before the server is initialized.
-
-This patch fixes all of these issues, and also threads the
-McRegionUpgrader to improve performance.
-
-1.18 update note: legacy region conversion has been entirely removed from the vanilla server
-
-diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java
-index 8703f97dc2f392b136c6851aa09b607cbfdfa5de..ade010fe3b62a4624b009c6d665e9909b2d314ac 100644
---- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java
-+++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java
-@@ -355,14 +355,14 @@ public class OldUsersConverter {
- }
-
- private void movePlayerFile(File playerDataFolder, String fileName, String uuid) {
-- File file5 = new File(file, fileName + ".dat");
-+ File file5 = new File(file, fileName + ".dat"); // Paper - diff on change
- File file6 = new File(playerDataFolder, uuid + ".dat");
-
- // CraftBukkit start - Use old file name to seed lastKnownName
- CompoundTag root = null;
-
- try {
-- root = NbtIo.readCompressed(new java.io.FileInputStream(file5));
-+ root = NbtIo.readCompressed(new java.io.FileInputStream(file5)); // Paper - diff on change
- } catch (Exception exception) {
- exception.printStackTrace();
- ServerInternalException.reportInternalException(exception); // Paper
-@@ -376,7 +376,7 @@ public class OldUsersConverter {
- data.putString("lastKnownName", fileName);
-
- try {
-- NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2));
-+ NbtIo.writeCompressed(root, new java.io.FileOutputStream(file5)); // Paper - write to correct path (diff on change)
- } catch (Exception exception) {
- exception.printStackTrace();
- ServerInternalException.reportInternalException(exception); // Paper
-diff --git a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java
-index af8a555c777b5abbaa2615d2ff03f03a9a93847e..b794c02ea36bdc901b1f6a160095abb3fcfe9b60 100644
---- a/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java
-+++ b/src/main/java/net/minecraft/world/level/storage/LevelStorageSource.java
-@@ -348,6 +348,12 @@ public class LevelStorageSource {
- });
- }
-
-+ // Paper start - copied from vanilla before below CB diff
-+ public File getDimensionPathForLegacyConversion(ResourceKey<Level> key) {
-+ return DimensionType.getStorageFolder(key, this.levelPath.toFile());
-+ }
-+ // Paper end
-+
- public File getDimensionPath(ResourceKey<Level> key) {
- return LevelStorageSource.getFolder(this.levelPath.toFile(), this.dimensionType); // CraftBukkit
- }
-diff --git a/src/main/java/net/minecraft/world/level/storage/McRegionUpgrader.java b/src/main/java/net/minecraft/world/level/storage/McRegionUpgrader.java
-index 87705fc8ee32016bf5ca533eb278bf32df08d3a3..b9bcbcf509ebb59d15082ce0faef8e405c16bdcc 100644
---- a/src/main/java/net/minecraft/world/level/storage/McRegionUpgrader.java
-+++ b/src/main/java/net/minecraft/world/level/storage/McRegionUpgrader.java
-@@ -35,13 +35,29 @@ public class McRegionUpgrader {
- private static final String MCREGION_EXTENSION = ".mcr";
-
- static boolean convertLevel(LevelStorageSource.LevelStorageAccess storageSession, ProgressListener progressListener) {
-+ // Paper start
-+ progressListener = new ProgressListener() {
-+ @Override
-+ public void progressStartNoAbort(net.minecraft.network.chat.Component title) {}
-+ @Override
-+ public void progressStart(net.minecraft.network.chat.Component title) {}
-+ @Override
-+ public void progressStage(net.minecraft.network.chat.Component task) {}
-+ @Override
-+ public void progressStagePercentage(int percentage) {}
-+ @Override
-+ public void stop() {}
-+ };
-+ // Paper end
- progressListener.progressStagePercentage(0);
- List<File> list = Lists.newArrayList();
- List<File> list2 = Lists.newArrayList();
- List<File> list3 = Lists.newArrayList();
-- File file = storageSession.getDimensionPath(Level.OVERWORLD);
-- File file2 = storageSession.getDimensionPath(Level.NETHER);
-- File file3 = storageSession.getDimensionPath(Level.END);
-+ // Paper start
-+ File file = storageSession.getDimensionPathForLegacyConversion(Level.OVERWORLD);
-+ File file2 = storageSession.getDimensionPathForLegacyConversion(Level.NETHER);
-+ File file3 = storageSession.getDimensionPathForLegacyConversion(Level.END);
-+ // Paper end
- LOGGER.info("Scanning folders...");
- addRegionFiles(file, list);
- if (file2.exists()) {
-@@ -88,14 +104,58 @@ public class McRegionUpgrader {
- }
-
- private static void convertRegions(RegistryAccess.RegistryHolder registryManager, File directory, Iterable<File> files, BiomeSource biomeSource, int i, int j, ProgressListener progressListener) {
-- for(File file : files) {
-- convertRegion(registryManager, directory, file, biomeSource, i, j, progressListener);
-- ++i;
-- int k = (int)Math.round(100.0D * (double)i / (double)j);
-- progressListener.progressStagePercentage(k);
-+ // Paper start - thread this because it's dead simple
-+ convertRegionsThreaded(registryManager, directory, files, biomeSource, i, j, progressListener);
-+ }
-+
-+ private static void convertRegionsThreaded(RegistryAccess.RegistryHolder registryManager, File directory, Iterable<File> files, BiomeSource biomeSource, int progress, int total, ProgressListener progressListener) {
-+ if (!files.iterator().hasNext()) {
-+ return;
- }
-
-+ final int threads = Runtime.getRuntime().availableProcessors() / 2;
-+ final java.util.concurrent.ExecutorService threadPool = java.util.concurrent.Executors.newFixedThreadPool(Math.max(1, threads), new java.util.concurrent.ThreadFactory() {
-+ private final java.util.concurrent.atomic.AtomicInteger threadCounter = new java.util.concurrent.atomic.AtomicInteger();
-+
-+ @Override
-+ public Thread newThread(final Runnable run) {
-+ final Thread ret = new Thread(run);
-+
-+ ret.setName("World upgrader thread for directory " + directory + " #" + this.threadCounter.getAndIncrement());
-+ ret.setUncaughtExceptionHandler((thread, throwable) -> {
-+ LOGGER.fatal("Error upgrading world", throwable);
-+ });
-+
-+ return ret;
-+ }
-+ });
-+ final java.util.concurrent.atomic.AtomicInteger converted = new java.util.concurrent.atomic.AtomicInteger(progress);
-+
-+ final long start = System.nanoTime();
-+
-+ for (final File file : files) {
-+ threadPool.execute(() -> {
-+ convertRegion(registryManager, directory, file, biomeSource, 0, total, progressListener);
-+ converted.incrementAndGet();
-+ });
-+ }
-+ threadPool.shutdown();
-+
-+ final java.text.DecimalFormat format = new java.text.DecimalFormat("#0.00");
-+ while (!threadPool.isTerminated()) {
-+ final int getConverted = converted.get();
-+ LOGGER.info("Converting... {}/{} ({}%)", getConverted, total, format.format(100.0D * (double) getConverted / (double) total));
-+ try {
-+ Thread.sleep(1000L);
-+ } catch (final InterruptedException ignored) {}
-+ }
-+
-+ final long end = System.nanoTime();
-+
-+ final double durationSeconds = Math.ceil((end - start) * 1.0e-9);
-+ LOGGER.info("Conversion for {} completed in {}s", directory, durationSeconds);
- }
-+ // Paper end
-
- private static void convertRegion(RegistryAccess.RegistryHolder registryManager, File directory, File file, BiomeSource biomeSource, int i, int j, ProgressListener progressListener) {
- String string = file.getName();
-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 4d0af984490b556a9911c3b8fdca1e168e6fe932..c20e5d69b4ad8adcdaffb56e4e2a24596ae16edf 100644
---- a/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java
-+++ b/src/main/java/net/minecraft/world/level/storage/PrimaryLevelData.java
-@@ -212,7 +212,7 @@ public class PrimaryLevelData implements ServerLevelData, WorldData {
- levelTag.putUUID("WanderingTraderId", this.wanderingTraderId);
- }
-
-- levelTag.putString("Bukkit.Version", Bukkit.getName() + "/" + Bukkit.getVersion() + "/" + Bukkit.getBukkitVersion()); // CraftBukkit
-+ if (Bukkit.getServer() != null) levelTag.putString("Bukkit.Version", Bukkit.getName() + "/" + Bukkit.getVersion() + "/" + Bukkit.getBukkitVersion()); // CraftBukkit // Paper - server may not be started yet
- }
-
- @Override
diff --git a/patches/removed/1.18/No longer needed/0781-Use-hash-table-for-maintaing-changed-block-set.patch b/patches/removed/1.18/No longer needed/0781-Use-hash-table-for-maintaing-changed-block-set.patch
deleted file mode 100644
index 568e3dbe25..0000000000
--- a/patches/removed/1.18/No longer needed/0781-Use-hash-table-for-maintaing-changed-block-set.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Thu, 11 Mar 2021 21:17:02 -0800
-Subject: [PATCH] Use hash table for maintaing changed block set
-
-When a lot of block changes occur the iteration for checking can
-add up a bit and cause a small performance impact.
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index de0c6316c9b75a2ecc7d6abf7bcca24e25de0ac0..4588ae8037407b81c99135863eb0c4e97c564c24 100644
---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -41,6 +41,8 @@ import net.minecraft.world.level.lighting.LevelLightEngine;
- import net.minecraft.server.MinecraftServer;
- // CraftBukkit end
-
-+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; // Paper
-+
- public class ChunkHolder {
-
- public static final Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> UNLOADED_CHUNK = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED);
-@@ -390,7 +392,7 @@ public class ChunkHolder {
- if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296
- if (this.changedBlocksPerSection[i] == null) {
- this.hasChangedSections = true;
-- this.changedBlocksPerSection[i] = new ShortArraySet();
-+ this.changedBlocksPerSection[i] = new ShortOpenHashSet(); // Paper - use a set to make setting constant-time
- }
-
- this.changedBlocksPerSection[i].add(SectionPos.sectionRelativePos(pos));
diff --git a/patches/removed/1.19.1/0906-Untrash-chat-handling.patch b/patches/removed/1.19.1/0906-Untrash-chat-handling.patch
deleted file mode 100644
index 069ec19760..0000000000
--- a/patches/removed/1.19.1/0906-Untrash-chat-handling.patch
+++ /dev/null
@@ -1,100 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Nassim Jahnke <[email protected]>
-Date: Wed, 8 Jun 2022 21:30:34 +0200
-Subject: [PATCH] Untrash chat handling
-
-Vanilla technically allows chat messages with starting slashes now,
-Spigot still accepts them as commands, most likely due to being too
-lazy to properly differentiate between chat and command intent in
-their implementation. This disallows modified clients to send chat
-messages with slashes and makes sure chat validation always happens
-on the netty event loop, rather than there and possibly being moved
-to the main thread, thus having the delayed handling cause a bad
-process order of message ids.
-
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundChatPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundChatPacket.java
-index 3825aa2c381a3ee77e05bea520ff5fb828733857..4e9832d5753b98621e68246ffc5d80c86a3b5ed3 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ServerboundChatPacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundChatPacket.java
-@@ -40,24 +40,16 @@ public class ServerboundChatPacket implements Packet<ServerGamePacketListener> {
- }
-
- // Spigot Start
-- private static final java.util.concurrent.ExecutorService executors = java.util.concurrent.Executors.newCachedThreadPool(
-+ // Paper start - untrash chat event handling
-+ public static final java.util.concurrent.ExecutorService executors = java.util.concurrent.Executors.newCachedThreadPool(
- new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon( true ).setNameFormat( "Async Chat Thread - #%d" ).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build() ); // Paper
- public void handle(final ServerGamePacketListener listener) {
-- if ( !this.message.startsWith("/") )
-- {
-- ServerboundChatPacket.executors.execute( new Runnable() // Paper - Use #execute to propagate exceptions up instead of swallowing them
-- {
--
-- @Override
-- public void run()
-- {
-- listener.handleChat( ServerboundChatPacket.this );
-- }
-- } );
-+ if (this.message.startsWith("/")) {
-+ // Just don't allow this instead of creating gigantic diff when untrashing more Spigot code
- return;
- }
-- // Spigot End
- listener.handleChat(this);
-+ // Paper end - untrash chat event handling
- }
-
- public String getMessage() {
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 28c76e7b3260ce56421057b88c3555f18b29da06..8e6045c51c46b8d8fa6f4b7fbe4b9478693bac82 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -2104,11 +2104,6 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
-
- @Override
- public void handleChat(ServerboundChatPacket packet) {
-- // CraftBukkit start - async chat
-- // SPIGOT-3638
-- if (this.server.isStopped()) {
-- return;
-- }
- // CraftBukkit end
- if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.getMessage())) {
- this.server.scheduleOnMain(() -> { // Paper - push to main for event firing
-@@ -2118,8 +2113,15 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
- if (this.tryHandleChat(packet.getMessage(), packet.getTimeStamp())) {
- // CraftBukkit start
- // this.filterTextPacket(packetplayinchat.getMessage(), (filteredtext) -> {
-+ // Paper start - untrash chat handling
-+ ServerboundChatPacket.executors.execute(() -> {
-+ if (this.server.isStopped()) {
-+ return;
-+ }
- this.handleChat(packet, FilteredText.passThrough(packet.getMessage())); // CraftBukkit - filter NYI
- // });
-+ });
-+ // Paper end - untrash chat handling
- // CraftBukkit end
- }
-
-@@ -2133,8 +2135,9 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"));
- }); // Paper - push to main for event firing
- } else {
-- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel());
-+ // Paper start - untrash chat handling
- if (this.tryHandleChat(packet.command(), packet.timeStamp())) {
-+ this.server.scheduleOnMain(() -> {
- // CraftBukkit start
- // CommandListenerWrapper commandlistenerwrapper = this.player.createCommandSourceStack().withSigningContext(serverboundchatcommandpacket.signingContext(this.player.getUUID()));
-
-@@ -2147,6 +2150,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser
- }
- this.detectRateSpam(true, "/" + packet.command()); // Spigot
- // CraftBukkit end
-+ });
-+ // Paper end - untrash chat handling
- }
-
- }
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0014-ChunkMapDistance-CME.patch b/patches/removed/1.19.2-legacy-chunksystem/0014-ChunkMapDistance-CME.patch
deleted file mode 100644
index 956878857c..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0014-ChunkMapDistance-CME.patch
+++ /dev/null
@@ -1,84 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Wed, 29 May 2019 04:01:22 +0100
-Subject: [PATCH] ChunkMapDistance CME
-
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 0873134f1f6de0c372ba28b89a20302c9a0115d8..e30893d6cbe3b42338d04453d0f452babeb61d8a 100644
---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -73,6 +73,7 @@ public class ChunkHolder {
- private boolean resendLight;
- private CompletableFuture<Void> pendingFullStateConfirmation;
-
-+ boolean isUpdateQueued = false; // Paper
- private final ChunkMap chunkMap; // Paper
-
- // Paper start
-diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index f08089b8672454acf8c2309e850466b335248692..6181f675d7addde30f7018b4cd46fe061a14da51 100644
---- a/src/main/java/net/minecraft/server/level/DistanceManager.java
-+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -52,7 +52,16 @@ public abstract class DistanceManager {
- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8);
- private final TickingTracker tickingTicketsTracker = new TickingTracker();
- private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33);
-- final Set<ChunkHolder> chunksToUpdateFutures = Sets.newHashSet();
-+ // Paper start use a queue, but still keep unique requirement
-+ public final java.util.Queue<ChunkHolder> pendingChunkUpdates = new java.util.ArrayDeque<ChunkHolder>() {
-+ @Override
-+ public boolean add(ChunkHolder o) {
-+ if (o.isUpdateQueued) return true;
-+ o.isUpdateQueued = true;
-+ return super.add(o);
-+ }
-+ };
-+ // Paper end
- final ChunkTaskPriorityQueueSorter ticketThrottler;
- final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> ticketThrottlerInput;
- final ProcessorHandle<ChunkTaskPriorityQueueSorter.Release> ticketThrottlerReleaser;
-@@ -127,26 +136,14 @@ public abstract class DistanceManager {
- ;
- }
-
-- if (!this.chunksToUpdateFutures.isEmpty()) {
-- // CraftBukkit start
-- // Iterate pending chunk updates with protection against concurrent modification exceptions
-- java.util.Iterator<ChunkHolder> iter = this.chunksToUpdateFutures.iterator();
-- int expectedSize = this.chunksToUpdateFutures.size();
-- do {
-- ChunkHolder playerchunk = iter.next();
-- iter.remove();
-- expectedSize--;
--
-- playerchunk.updateFutures(chunkStorage, this.mainThreadExecutor);
--
-- // Reset iterator if set was modified using add()
-- if (this.chunksToUpdateFutures.size() != expectedSize) {
-- expectedSize = this.chunksToUpdateFutures.size();
-- iter = this.chunksToUpdateFutures.iterator();
-- }
-- } while (iter.hasNext());
-- // CraftBukkit end
--
-+ // Paper start
-+ if (!this.pendingChunkUpdates.isEmpty()) {
-+ while(!this.pendingChunkUpdates.isEmpty()) {
-+ ChunkHolder remove = this.pendingChunkUpdates.remove();
-+ remove.isUpdateQueued = false;
-+ remove.updateFutures(chunkStorage, this.mainThreadExecutor);
-+ }
-+ // Paper end
- return true;
- } else {
- if (!this.ticketsToRelease.isEmpty()) {
-@@ -471,7 +468,7 @@ public abstract class DistanceManager {
- if (k != level) {
- playerchunk = DistanceManager.this.updateChunkScheduling(id, level, playerchunk, k);
- if (playerchunk != null) {
-- DistanceManager.this.chunksToUpdateFutures.add(playerchunk);
-+ DistanceManager.this.pendingChunkUpdates.add(playerchunk);
- }
-
- }
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0015-Do-not-copy-visible-chunks.patch b/patches/removed/1.19.2-legacy-chunksystem/0015-Do-not-copy-visible-chunks.patch
deleted file mode 100644
index 0f5350c531..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0015-Do-not-copy-visible-chunks.patch
+++ /dev/null
@@ -1,122 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sun, 21 Mar 2021 11:22:10 -0700
-Subject: [PATCH] Do not copy visible chunks
-
-For servers with a lot of chunk holders, copying for each
-tickDistanceManager call can take up quite a bit in
-the function. I saw approximately 1/3rd of the function
-on the copy.
-
-diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java
-index 83dc09f6526206690c474b50a7a6e71cefc93ab4..7f76c304f5eb3c2f27b348918588ab67b795b1ba 100644
---- a/src/main/java/net/minecraft/server/ChunkSystem.java
-+++ b/src/main/java/net/minecraft/server/ChunkSystem.java
-@@ -202,19 +202,24 @@ public final class ChunkSystem {
- }
-
- public static List<ChunkHolder> getVisibleChunkHolders(final ServerLevel level) {
-- return new ArrayList<>(level.chunkSource.chunkMap.visibleChunkMap.values());
-+ if (Bukkit.isPrimaryThread()) {
-+ return level.chunkSource.chunkMap.updatingChunks.getVisibleValuesCopy();
-+ }
-+ synchronized (level.chunkSource.chunkMap.updatingChunks) {
-+ return level.chunkSource.chunkMap.updatingChunks.getVisibleValuesCopy();
-+ }
- }
-
- public static List<ChunkHolder> getUpdatingChunkHolders(final ServerLevel level) {
-- return new ArrayList<>(level.chunkSource.chunkMap.updatingChunkMap.values());
-+ return level.chunkSource.chunkMap.updatingChunks.getUpdatingValuesCopy();
- }
-
- public static int getVisibleChunkHolderCount(final ServerLevel level) {
-- return level.chunkSource.chunkMap.visibleChunkMap.size();
-+ return level.chunkSource.chunkMap.updatingChunks.getVisibleMap().size();
- }
-
- public static int getUpdatingChunkHolderCount(final ServerLevel level) {
-- return level.chunkSource.chunkMap.updatingChunkMap.size();
-+ return level.chunkSource.chunkMap.updatingChunks.getUpdatingMap().size();
- }
-
- public static boolean hasAnyChunkHolders(final ServerLevel level) {
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 2a9e5fb8164f79b0f9c1cb5497216e51f9df3454..ea27e6b1340a42c675bc68ed75f100569114be7a 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -121,9 +121,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- private static final int MIN_VIEW_DISTANCE = 3;
- public static final int MAX_VIEW_DISTANCE = 33;
- public static final int MAX_CHUNK_DISTANCE = 33 + ChunkStatus.maxDistance();
-+ // Paper start - Don't copy
-+ public final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<ChunkHolder> updatingChunks = new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>();
-+ // Paper end - Don't copy
- public static final int FORCED_TICKET_LEVEL = 31;
-- public final Long2ObjectLinkedOpenHashMap<ChunkHolder> updatingChunkMap = new Long2ObjectLinkedOpenHashMap();
-- public volatile Long2ObjectLinkedOpenHashMap<ChunkHolder> visibleChunkMap;
-+ // Paper - Don't copy
- private final Long2ObjectLinkedOpenHashMap<ChunkHolder> pendingUnloads;
- public final LongSet entitiesInLevel;
- public final ServerLevel level;
-@@ -224,7 +226,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
- super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
-- this.visibleChunkMap = this.updatingChunkMap.clone();
-+ // Paper - don't copy
- this.pendingUnloads = new Long2ObjectLinkedOpenHashMap();
- this.entitiesInLevel = new LongOpenHashSet();
- this.toDrop = new LongOpenHashSet();
-@@ -327,12 +329,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- @Nullable
- public ChunkHolder getUpdatingChunkIfPresent(long pos) {
-- return (ChunkHolder) this.updatingChunkMap.get(pos);
-+ return this.updatingChunks.getUpdating(pos); // Paper - Don't copy
- }
-
- @Nullable
- public ChunkHolder getVisibleChunkIfPresent(long pos) {
-- return (ChunkHolder) this.visibleChunkMap.get(pos);
-+ // Paper start - Don't copy
-+ if (Thread.currentThread() == this.level.thread) {
-+ return this.updatingChunks.getVisible(pos);
-+ }
-+ return this.updatingChunks.getVisibleAsync(pos);
-+ // Paper end - Don't copy
- }
-
- protected IntSupplier getChunkQueueLevel(long pos) {
-@@ -515,7 +522,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- // Paper start
- holder.onChunkAdd();
- // Paper end
-- this.updatingChunkMap.put(pos, holder);
-+ this.updatingChunks.queueUpdate(pos, holder); // Paper - Don't copy
- this.modified = true;
- }
-
-@@ -592,7 +599,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) {
- long j = longiterator.nextLong();
-- ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j);
-+ ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
-
- if (playerchunk != null) {
- playerchunk.onChunkRemove(); // Paper
-@@ -672,7 +679,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- if (!this.modified) {
- return false;
- } else {
-- this.visibleChunkMap = this.updatingChunkMap.clone();
-+ // Paper start - Don't copy
-+ synchronized (this.updatingChunks) {
-+ this.updatingChunks.performUpdates();
-+ }
-+ // Paper end - Don't copy
-+
- this.modified = false;
- return true;
- }
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0016-Chunk-debug-command.patch b/patches/removed/1.19.2-legacy-chunksystem/0016-Chunk-debug-command.patch
deleted file mode 100644
index 79581b2b72..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0016-Chunk-debug-command.patch
+++ /dev/null
@@ -1,432 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sat, 1 Jun 2019 13:00:55 -0700
-Subject: [PATCH] Chunk debug command
-
-Prints all chunk information to a text file into the debug
-folder in the root server folder. The format is in JSON, and
-the data format is described in MCUtil#dumpChunks(File)
-
-The command will output server version and all online players to the
-file as well. We do not log anything but the location, world and
-username of the player.
-
-Also logs the value of these config values (note not all are paper's):
-- keep spawn loaded value
-- spawn radius
-- view distance
-
-Each chunk has the following logged:
-- Coordinate
-- Ticket level & its corresponding state
-- Whether it is queued for unload
-- Chunk status (may be unloaded)
-- All tickets on the chunk
-
-Example log:
-https://gist.githubusercontent.com/Spottedleaf/0131e7710ffd5d531e5fd246c3367380/raw/169ae1b2e240485f99bc7a6bd8e78d90e3af7397/chunks-2019-06-01_19.57.05.txt
-
-For references on certain keywords (ticket, status, etc), please see:
-
-https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528273&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528273
-https://bugs.mojang.com/browse/MC-141484?focusedCommentId=528577&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-528577
-
-diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java
-index b3a58bf4b654e336826dc04da9e2f80ff8b9a9a7..8e773f522521d2dd6349c87b582a3337b76f161f 100644
---- a/src/main/java/io/papermc/paper/command/PaperCommand.java
-+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
-@@ -1,5 +1,6 @@
- package io.papermc.paper.command;
-
-+import io.papermc.paper.command.subcommands.ChunkDebugCommand;
- import io.papermc.paper.command.subcommands.EntityCommand;
- import io.papermc.paper.command.subcommands.HeapDumpCommand;
- import io.papermc.paper.command.subcommands.ReloadCommand;
-@@ -40,6 +41,7 @@ public final class PaperCommand extends Command {
- commands.put(Set.of("entity"), new EntityCommand());
- commands.put(Set.of("reload"), new ReloadCommand());
- commands.put(Set.of("version"), new VersionCommand());
-+ commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand());
-
- 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/ChunkDebugCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..28a9550449be9a212f054b02e43fbd8a3781efcf
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/command/subcommands/ChunkDebugCommand.java
-@@ -0,0 +1,166 @@
-+package io.papermc.paper.command.subcommands;
-+
-+import io.papermc.paper.command.CommandUtil;
-+import io.papermc.paper.command.PaperSubcommand;
-+import java.io.File;
-+import java.time.LocalDateTime;
-+import java.time.format.DateTimeFormatter;
-+import java.util.ArrayList;
-+import java.util.Collections;
-+import java.util.List;
-+import java.util.Locale;
-+import net.minecraft.server.MCUtil;
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ChunkHolder;
-+import net.minecraft.server.level.ServerLevel;
-+import org.bukkit.Bukkit;
-+import org.bukkit.command.CommandSender;
-+import org.bukkit.craftbukkit.CraftWorld;
-+import org.checkerframework.checker.nullness.qual.NonNull;
-+import org.checkerframework.checker.nullness.qual.Nullable;
-+import org.checkerframework.framework.qual.DefaultQualifier;
-+
-+import static net.kyori.adventure.text.Component.text;
-+import static net.kyori.adventure.text.format.NamedTextColor.BLUE;
-+import static net.kyori.adventure.text.format.NamedTextColor.DARK_AQUA;
-+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
-+import static net.kyori.adventure.text.format.NamedTextColor.RED;
-+
-+@DefaultQualifier(NonNull.class)
-+public final class ChunkDebugCommand implements PaperSubcommand {
-+ @Override
-+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
-+ switch (subCommand) {
-+ case "debug" -> this.doDebug(sender, args);
-+ case "chunkinfo" -> this.doChunkInfo(sender, args);
-+ }
-+ return true;
-+ }
-+
-+ @Override
-+ public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
-+ switch (subCommand) {
-+ case "debug" -> {
-+ if (args.length == 1) {
-+ return CommandUtil.getListMatchingLast(sender, args, "help", "chunks");
-+ }
-+ }
-+ case "chunkinfo" -> {
-+ List<String> worldNames = new ArrayList<>();
-+ worldNames.add("*");
-+ for (org.bukkit.World world : Bukkit.getWorlds()) {
-+ worldNames.add(world.getName());
-+ }
-+ if (args.length == 1) {
-+ return CommandUtil.getListMatchingLast(sender, args, worldNames);
-+ }
-+ }
-+ }
-+ return Collections.emptyList();
-+ }
-+
-+ private void doChunkInfo(final CommandSender sender, final String[] args) {
-+ List<org.bukkit.World> worlds;
-+ if (args.length < 1 || args[0].equals("*")) {
-+ worlds = Bukkit.getWorlds();
-+ } else {
-+ worlds = new ArrayList<>(args.length);
-+ for (final String arg : args) {
-+ org.bukkit.@Nullable World world = Bukkit.getWorld(arg);
-+ if (world == null) {
-+ sender.sendMessage(text("World '" + arg + "' is invalid", RED));
-+ return;
-+ }
-+ worlds.add(world);
-+ }
-+ }
-+
-+ int accumulatedTotal = 0;
-+ int accumulatedInactive = 0;
-+ int accumulatedBorder = 0;
-+ int accumulatedTicking = 0;
-+ int accumulatedEntityTicking = 0;
-+
-+ for (final org.bukkit.World bukkitWorld : worlds) {
-+ final ServerLevel world = ((CraftWorld) bukkitWorld).getHandle();
-+
-+ int total = 0;
-+ int inactive = 0;
-+ int border = 0;
-+ int ticking = 0;
-+ int entityTicking = 0;
-+
-+ for (final ChunkHolder chunk : net.minecraft.server.ChunkSystem.getVisibleChunkHolders(world)) {
-+ if (chunk.getFullChunkNowUnchecked() == null) {
-+ continue;
-+ }
-+
-+ ++total;
-+
-+ ChunkHolder.FullChunkStatus state = chunk.getFullStatus();
-+
-+ switch (state) {
-+ case INACCESSIBLE -> ++inactive;
-+ case BORDER -> ++border;
-+ case TICKING -> ++ticking;
-+ case ENTITY_TICKING -> ++entityTicking;
-+ }
-+ }
-+
-+ accumulatedTotal += total;
-+ accumulatedInactive += inactive;
-+ accumulatedBorder += border;
-+ accumulatedTicking += ticking;
-+ accumulatedEntityTicking += entityTicking;
-+
-+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text(bukkitWorld.getName(), GREEN), text(":")));
-+ sender.sendMessage(text().color(DARK_AQUA).append(
-+ text("Total: ", BLUE), text(total),
-+ text(" Inactive: ", BLUE), text(inactive),
-+ text(" Border: ", BLUE), text(border),
-+ text(" Ticking: ", BLUE), text(ticking),
-+ text(" Entity: ", BLUE), text(entityTicking)
-+ ));
-+ }
-+ if (worlds.size() > 1) {
-+ sender.sendMessage(text().append(text("Chunks in ", BLUE), text("all listed worlds", GREEN), text(":", DARK_AQUA)));
-+ sender.sendMessage(text().color(DARK_AQUA).append(
-+ text("Total: ", BLUE), text(accumulatedTotal),
-+ text(" Inactive: ", BLUE), text(accumulatedInactive),
-+ text(" Border: ", BLUE), text(accumulatedBorder),
-+ text(" Ticking: ", BLUE), text(accumulatedTicking),
-+ text(" Entity: ", BLUE), text(accumulatedEntityTicking)
-+ ));
-+ }
-+ }
-+
-+ private void doDebug(final CommandSender sender, final String[] args) {
-+ if (args.length < 1) {
-+ sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED));
-+ return;
-+ }
-+
-+ final String debugType = args[0].toLowerCase(Locale.ENGLISH);
-+ switch (debugType) {
-+ case "chunks" -> {
-+ if (args.length >= 2 && args[1].toLowerCase(Locale.ENGLISH).equals("help")) {
-+ sender.sendMessage(text("Use /paper debug chunks [world] to dump loaded chunk information to a file", RED));
-+ break;
-+ }
-+ File file = new File(new File(new File("."), "debug"),
-+ "chunks-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt");
-+ sender.sendMessage(text("Writing chunk information dump to " + file, GREEN));
-+ try {
-+ MCUtil.dumpChunks(file);
-+ sender.sendMessage(text("Successfully written chunk information!", GREEN));
-+ } catch (Throwable thr) {
-+ MinecraftServer.LOGGER.warn("Failed to dump chunk information to file " + file.toString(), thr);
-+ sender.sendMessage(text("Failed to dump chunk information, see console", RED));
-+ }
-+ }
-+ // "help" & default
-+ default -> sender.sendMessage(text("Use /paper debug [chunks] help for more information on a specific command", RED));
-+ }
-+ }
-+
-+}
-diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
-index b310d51b7fe3e8cef0a450674725969fe1ce78a4..2e56c52e3ee45b0304a9e6a5eab863ef96b2aab0 100644
---- a/src/main/java/net/minecraft/server/MCUtil.java
-+++ b/src/main/java/net/minecraft/server/MCUtil.java
-@@ -1,15 +1,27 @@
- package net.minecraft.server;
-
- import com.google.common.util.concurrent.ThreadFactoryBuilder;
-+import com.google.gson.JsonArray;
-+import com.google.gson.JsonObject;
-+import com.google.gson.internal.Streams;
-+import com.google.gson.stream.JsonWriter;
-+import com.mojang.datafixers.util.Either;
- import it.unimi.dsi.fastutil.objects.ObjectRBTreeSet;
- import java.lang.ref.Cleaner;
- import net.minecraft.core.BlockPos;
- import net.minecraft.core.Direction;
-+import net.minecraft.server.level.ChunkHolder;
-+import net.minecraft.server.level.ChunkMap;
-+import net.minecraft.server.level.DistanceManager;
- import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.server.level.Ticket;
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.level.ChunkPos;
- import net.minecraft.world.level.ClipContext;
- import net.minecraft.world.level.Level;
-+import net.minecraft.world.level.chunk.ChunkAccess;
-+import net.minecraft.world.level.chunk.ChunkStatus;
- import org.apache.commons.lang.exception.ExceptionUtils;
- import org.bukkit.Location;
- import org.bukkit.block.BlockFace;
-@@ -19,8 +31,11 @@ import org.spigotmc.AsyncCatcher;
-
- import javax.annotation.Nonnull;
- import javax.annotation.Nullable;
-+import java.io.*;
-+import java.nio.charset.StandardCharsets;
- import java.util.List;
- import java.util.Queue;
-+import java.util.Set;
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.LinkedBlockingQueue;
-@@ -505,6 +520,163 @@ public final class MCUtil {
- }
- }
-
-+ public static ChunkStatus getChunkStatus(ChunkHolder chunk) {
-+ return chunk.getChunkHolderStatus();
-+ }
-+
-+ public static void dumpChunks(File file) throws IOException {
-+ file.getParentFile().mkdirs();
-+ file.createNewFile();
-+ /*
-+ * Json format:
-+ *
-+ * Main data format:
-+ * -server-version:<string>
-+ * -data-version:<int>
-+ * -worlds:
-+ * -name:<world name>
-+ * -view-distance:<int>
-+ * -keep-spawn-loaded:<boolean>
-+ * -keep-spawn-loaded-range:<int>
-+ * -visible-chunk-count:<int>
-+ * -loaded-chunk-count:<int>
-+ * -verified-fully-loaded-chunks:<int>
-+ * -players:<array of player>
-+ * -chunk-data:<array of chunks>
-+ *
-+ * Player format:
-+ * -name:<string>
-+ * -x:<double>
-+ * -y:<double>
-+ * -z:<double>
-+ *
-+ * Chunk Format:
-+ * -x:<integer>
-+ * -z:<integer>
-+ * -ticket-level:<integer>
-+ * -state:<string>
-+ * -queued-for-unload:<boolean>
-+ * -status:<string>
-+ * -tickets:<array of tickets>
-+ *
-+ *
-+ * Ticket format:
-+ * -ticket-type:<string>
-+ * -ticket-level:<int>
-+ * -add-tick:<long>
-+ * -object-reason:<string> // This depends on the type of ticket. ie POST_TELEPORT -> entity id
-+ */
-+ List<org.bukkit.World> worlds = org.bukkit.Bukkit.getWorlds();
-+ JsonObject data = new JsonObject();
-+
-+ data.addProperty("server-version", org.bukkit.Bukkit.getVersion());
-+ data.addProperty("data-version", 0);
-+
-+ JsonArray worldsData = new JsonArray();
-+
-+ for (org.bukkit.World bukkitWorld : worlds) {
-+ JsonObject worldData = new JsonObject();
-+
-+ ServerLevel world = ((org.bukkit.craftbukkit.CraftWorld)bukkitWorld).getHandle();
-+ ChunkMap chunkMap = world.getChunkSource().chunkMap;
-+ DistanceManager chunkMapDistance = chunkMap.distanceManager;
-+ List<ChunkHolder> allChunks = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(world);
-+ List<ServerPlayer> players = world.players;
-+
-+ int fullLoadedChunks = 0;
-+
-+ for (ChunkHolder chunk : allChunks) {
-+ if (chunk.getFullChunkNowUnchecked() != null) {
-+ ++fullLoadedChunks;
-+ }
-+ }
-+
-+ // sorting by coordinate makes the log easier to read
-+ allChunks.sort((ChunkHolder v1, ChunkHolder v2) -> {
-+ if (v1.pos.x != v2.pos.x) {
-+ return Integer.compare(v1.pos.x, v2.pos.x);
-+ }
-+ return Integer.compare(v1.pos.z, v2.pos.z);
-+ });
-+
-+ worldData.addProperty("name", world.getWorld().getName());
-+ worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
-+ worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
-+ worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16);
-+ worldData.addProperty("visible-chunk-count", allChunks.size());
-+ worldData.addProperty("loaded-chunk-count", chunkMap.entitiesInLevel.size());
-+ worldData.addProperty("verified-fully-loaded-chunks", fullLoadedChunks);
-+
-+ JsonArray playersData = new JsonArray();
-+
-+ for (ServerPlayer player : players) {
-+ JsonObject playerData = new JsonObject();
-+
-+ playerData.addProperty("name", player.getScoreboardName());
-+ playerData.addProperty("x", player.getX());
-+ playerData.addProperty("y", player.getY());
-+ playerData.addProperty("z", player.getZ());
-+
-+ playersData.add(playerData);
-+
-+ }
-+
-+ worldData.add("players", playersData);
-+
-+ JsonArray chunksData = new JsonArray();
-+
-+ for (ChunkHolder playerChunk : allChunks) {
-+ JsonObject chunkData = new JsonObject();
-+
-+ Set<Ticket<?>> tickets = chunkMapDistance.tickets.get(playerChunk.pos.longKey);
-+ ChunkStatus status = getChunkStatus(playerChunk);
-+
-+ chunkData.addProperty("x", playerChunk.pos.x);
-+ chunkData.addProperty("z", playerChunk.pos.z);
-+ chunkData.addProperty("ticket-level", playerChunk.getTicketLevel());
-+ chunkData.addProperty("state", ChunkHolder.getFullChunkStatus(playerChunk.getTicketLevel()).toString());
-+ chunkData.addProperty("queued-for-unload", chunkMap.toDrop.contains(playerChunk.pos.longKey));
-+ chunkData.addProperty("status", status == null ? "unloaded" : status.toString());
-+
-+ JsonArray ticketsData = new JsonArray();
-+
-+ if (tickets != null) {
-+ for (Ticket<?> ticket : tickets) {
-+ JsonObject ticketData = new JsonObject();
-+
-+ ticketData.addProperty("ticket-type", ticket.getType().toString());
-+ ticketData.addProperty("ticket-level", ticket.getTicketLevel());
-+ ticketData.addProperty("object-reason", String.valueOf(ticket.key));
-+ ticketData.addProperty("add-tick", ticket.createdTick);
-+
-+ ticketsData.add(ticketData);
-+ }
-+ }
-+
-+ chunkData.add("tickets", ticketsData);
-+ chunksData.add(chunkData);
-+ }
-+
-+
-+ worldData.add("chunk-data", chunksData);
-+ worldsData.add(worldData);
-+ }
-+
-+ data.add("worlds", worldsData);
-+
-+ StringWriter stringWriter = new StringWriter();
-+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
-+ jsonWriter.setIndent(" ");
-+ jsonWriter.setLenient(false);
-+ Streams.write(data, jsonWriter);
-+
-+ String fileData = stringWriter.toString();
-+
-+ try (PrintStream out = new PrintStream(new FileOutputStream(file), false, StandardCharsets.UTF_8)) {
-+ out.print(fileData);
-+ }
-+ }
-+
- public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) {
- return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status);
- }
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0017-Make-CallbackExecutor-strict-again.patch b/patches/removed/1.19.2-legacy-chunksystem/0017-Make-CallbackExecutor-strict-again.patch
deleted file mode 100644
index e37cf530bb..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0017-Make-CallbackExecutor-strict-again.patch
+++ /dev/null
@@ -1,48 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Fri, 24 Apr 2020 09:06:15 -0700
-Subject: [PATCH] Make CallbackExecutor strict again
-
-The correct fix for double scheduling is to avoid it. The reason
-this class is used is because double scheduling causes issues
-elsewhere, and it acts as an explicit detector of what double
-schedules. Effectively, use the callback executor as a tool of
-finding issues rather than hiding these issues.
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index ea27e6b1340a42c675bc68ed75f100569114be7a..4da0cbe58dad0f66e0d056c71684120514dcac6a 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -157,17 +157,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- public final CallbackExecutor callbackExecutor = new CallbackExecutor();
- public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable {
-
-- private final java.util.Queue<Runnable> queue = new java.util.ArrayDeque<>();
-+ private Runnable queued; // Paper - revert CB changes
-
- @Override
- public void execute(Runnable runnable) {
-- this.queue.add(runnable);
-+ // Paper start - revert CB changes
-+ org.spigotmc.AsyncCatcher.catchOp("Callback Executor execute");
-+ if (this.queued != null) {
-+ LOGGER.error("Failed to schedule runnable", new IllegalStateException("Already queued"));
-+ throw new IllegalStateException("Already queued");
-+ }
-+ this.queued = runnable;
-+ // Paper end - revert CB changes
- }
-
- @Override
- public void run() {
-- Runnable task;
-- while ((task = this.queue.poll()) != null) {
-+ // Paper start - revert CB changes
-+ org.spigotmc.AsyncCatcher.catchOp("Callback Executor execute");
-+ Runnable task = this.queued;
-+ if (task != null) {
-+ this.queued = null;
-+ // Paper end - revert CB changes
- task.run();
- }
- }
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0019-Asynchronous-chunk-IO-and-loading.patch b/patches/removed/1.19.2-legacy-chunksystem/0019-Asynchronous-chunk-IO-and-loading.patch
deleted file mode 100644
index 0f9c62fcb5..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0019-Asynchronous-chunk-IO-and-loading.patch
+++ /dev/null
@@ -1,3516 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sat, 13 Jul 2019 09:23:10 -0700
-Subject: [PATCH] Asynchronous chunk IO and loading
-
-ChunkSerializer needs the new tick lists to be saved (see added todos)
-
-This patch re-adds a file IO thread as well as shoving de-serializing
-chunk NBT data onto worker threads. This patch also will shove
-chunk data serialization onto the same worker threads when the chunk
-is unloaded - this cannot be done for regular saves since that's unsafe.
-
-The file IO Thread
-
-Unlike 1.13 and below, the file IO thread is prioritized - IO tasks can
-be reoredered, however they are "stuck" to a world & coordinate.
-
-Scheduling IO tasks works as follows, given a world & coordinate - location:
-
-The IO thread has been designed to ensure that reads and writes appear to
-occur synchronously for a given location, however the implementation also
-has the unfortunate side-effect of making every write appear as if
-they occur without failure.
-
-The IO thread has also been designed to accomodate Mojang's decision to
-store chunk data and POI data separately. It can independently schedule
-tasks for each.
-
-However threads can wait for writes to complete and check if:
- - The write was overwriten by another scheduler
- - The write failed (however it does not indicate whether it was overwritten by another scheduler)
-
-Scheduling reads:
-
- - If a write task is in progress, the task is not scheduled and returns the in-progress write data
- This means that readers cannot modify the NBTTagCompound returned and must clone if it they wish to write
- - If a write task is not in progress but a read task is in progress, then the read task is simply chained
- This means that again, readers cannot modify the NBTTagCompound returned
-
-Scheduling writes:
-
- - If a read task is in progress, ignore the read task and schedule the write
- We cannot complete the read task since we assume it wants old data - not current
- - If a write task is pending, overwrite the write data
- The file IO thread does correctly handle cases where the data is overwritten when it
- is writing data (before completing a task it will check if the data was overwritten and
- will retry).
-
-When the file IO thread executes a task for a location, the it will
-execute the read task first (if it exists), then it will execute the
-write task. This ensures that, even when scheduling at different
-priorities, that reads/writes for a location act synchronously.
-
-The downside of the file IO thread is that write failure can only be
-indicated to the scheduling thread if:
-
-- No other thread decides to schedule another write for the location
-concurrently
-- The scheduling thread blocks on the write to complete (however the
-current implementation can be modified to indicate success
-asynchronously)
-
-The file io thread can be modified easily to provide indications
-of write failure and write overwriting if needed.
-
-The upside of the file IO thread is that if a write failures, then
-chunk data is not lost until server restart. This leaves more room
-for spurious failure.
-
-Finally, the io thread will indicate to the console when reads
-or writes fail - with relevant detail.
-
-Asynchronous chunk data serialization for unloading chunks
-
-When chunks unload they make a call to PlayerChunkMap#saveChunk(IChunkAccess).
-Even if I make the IO asynchronous for this call, the data serialization
-still hits pretty hard. And given that now the chunk system will
-aggressively unload chunks more often (queued immediately at
-ticket level 45 or higher), unloads occur more often, and
-combined with our changes to the unload queue to make it
-significantly more aggresive - chunk unloads can hit pretty hard.
-Especially players running around with elytras and fireworks.
-
-For serializing chunk data off main, there are some tasks which cannot be
-done asynchronously. Lighting data must be saved beforehand as well as
-potentially some tick lists. These are completed before scheduling the
-asynchronous save.
-
-However serializing chunk data off of the main thread is still risky.
-Even though this patch schedules the save to occur after ALL references
-of the chunk are removed from the world, plugins can still technically
-access entities inside the chunks. For this, if the serialization task
-fails for any reason, it will be re-scheduled to be serialized on the
-main thread - with the hopes that the reason it failed was due to a plugin
-and not an error with the save code itself. Like vanilla code - if the
-serialization fails, the chunk data is lost.
-
-Asynchronous chunk io/loading
-
-Mojang's current implementation for loading chunk data off disk is
-to return a CompletableFuture that will be completed by scheduling a
-task to be executed on the world's chunk queue (which is only drained
-on the main thread). This task will read the IO off disk and it will
-apply data conversions & deserialization synchronously. Obviously
-all 3 of these operations are expensive however all can be completed
-asynchronously instead.
-
-The solution this patch uses is as follows:
-
-0. If an asynchronous chunk save is in progress (see above), wait
-for that task to complete. It will use the serialized NBTTagCompound
-created by the task. If the task fails to complete, then we would continue
-with step 1. If it does not, we skip step 1. (Note: We actually load
-POI data no matter what in this case).
-1. Schedule an IO task to read chunk & poi data off disk.
-2. The IO task will schedule a chunk load task.
-3. The chunk load task executes on the async chunk loader threads
-and will apply datafixers & de-serialize the chunk into a ProtoChunk
-or ProtoChunkExtension.
-4. The in progress chunk is then passed on to the world's chunk queue
-to complete the ComletableFuture and execute any of the synchronous
-tasks required to be executed by the chunk load task (i.e lighting
-and some poi tasks).
-
-diff --git a/src/main/java/co/aikar/timings/WorldTimingsHandler.java b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
-index 0fda52841b5e1643efeda92106124998abc4e0aa..fe79c0add4f7cb18d487c5bb9415c40c5b551ea2 100644
---- a/src/main/java/co/aikar/timings/WorldTimingsHandler.java
-+++ b/src/main/java/co/aikar/timings/WorldTimingsHandler.java
-@@ -58,6 +58,16 @@ public class WorldTimingsHandler {
-
- public final Timing miscMobSpawning;
-
-+ public final Timing poiUnload;
-+ public final Timing chunkUnload;
-+ public final Timing poiSaveDataSerialization;
-+ public final Timing chunkSave;
-+ public final Timing chunkSaveDataSerialization;
-+ public final Timing chunkSaveIOWait;
-+ public final Timing chunkUnloadPrepareSave;
-+ public final Timing chunkUnloadPOISerialization;
-+ public final Timing chunkUnloadDataSave;
-+
- public WorldTimingsHandler(Level server) {
- String name = ((PrimaryLevelData) server.getLevelData()).getLevelName() + " - ";
-
-@@ -111,6 +121,16 @@ public class WorldTimingsHandler {
-
-
- miscMobSpawning = Timings.ofSafe(name + "Mob spawning - Misc");
-+
-+ poiUnload = Timings.ofSafe(name + "Chunk unload - POI");
-+ chunkUnload = Timings.ofSafe(name + "Chunk unload - Chunk");
-+ poiSaveDataSerialization = Timings.ofSafe(name + "Chunk save - POI Data serialization");
-+ chunkSave = Timings.ofSafe(name + "Chunk save - Chunk");
-+ chunkSaveDataSerialization = Timings.ofSafe(name + "Chunk save - Chunk Data serialization");
-+ chunkSaveIOWait = Timings.ofSafe(name + "Chunk save - Chunk IO Wait");
-+ chunkUnloadPrepareSave = Timings.ofSafe(name + "Chunk unload - Async Save Prepare");
-+ chunkUnloadPOISerialization = Timings.ofSafe(name + "Chunk unload - POI Data Serialization");
-+ chunkUnloadDataSave = Timings.ofSafe(name + "Chunk unload - Data Serialization");
- }
-
- public static Timing getTickList(ServerLevel worldserver, String timingsType) {
-diff --git a/src/main/java/com/destroystokyo/paper/io/IOUtil.java b/src/main/java/com/destroystokyo/paper/io/IOUtil.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..5af0ac3d9e87c06053e65433060f15779c156c2a
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/io/IOUtil.java
-@@ -0,0 +1,62 @@
-+package com.destroystokyo.paper.io;
-+
-+import org.bukkit.Bukkit;
-+
-+public final class IOUtil {
-+
-+ /* Copied from concrete or concurrentutil */
-+
-+ public static long getCoordinateKey(final int x, final int z) {
-+ return ((long)z << 32) | (x & 0xFFFFFFFFL);
-+ }
-+
-+ public static int getCoordinateX(final long key) {
-+ return (int)key;
-+ }
-+
-+ public static int getCoordinateZ(final long key) {
-+ return (int)(key >>> 32);
-+ }
-+
-+ public static int getRegionCoordinate(final int chunkCoordinate) {
-+ return chunkCoordinate >> 5;
-+ }
-+
-+ public static int getChunkInRegion(final int chunkCoordinate) {
-+ return chunkCoordinate & 31;
-+ }
-+
-+ public static String genericToString(final Object object) {
-+ return object == null ? "null" : object.getClass().getName() + ":" + object.toString();
-+ }
-+
-+ public static <T> T notNull(final T obj) {
-+ if (obj == null) {
-+ throw new NullPointerException();
-+ }
-+ return obj;
-+ }
-+
-+ public static <T> T notNull(final T obj, final String msgIfNull) {
-+ if (obj == null) {
-+ throw new NullPointerException(msgIfNull);
-+ }
-+ return obj;
-+ }
-+
-+ public static void arrayBounds(final int off, final int len, final int arrayLength, final String msgPrefix) {
-+ if (off < 0 || len < 0 || (arrayLength - off) < len) {
-+ throw new ArrayIndexOutOfBoundsException(msgPrefix + ": off: " + off + ", len: " + len + ", array length: " + arrayLength);
-+ }
-+ }
-+
-+ public static int getPriorityForCurrentThread() {
-+ return Bukkit.isPrimaryThread() ? PrioritizedTaskQueue.HIGHEST_PRIORITY : PrioritizedTaskQueue.NORMAL_PRIORITY;
-+ }
-+
-+ @SuppressWarnings("unchecked")
-+ public static <T extends Throwable> void rethrow(final Throwable throwable) throws T {
-+ throw (T)throwable;
-+ }
-+
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java b/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..7c89a96d54641904e2d4562fe28c59deecfb5444
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/io/PaperFileIOThread.java
-@@ -0,0 +1,596 @@
-+package com.destroystokyo.paper.io;
-+
-+import com.mojang.logging.LogUtils;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.world.level.ChunkPos;
-+import net.minecraft.world.level.chunk.storage.RegionFile;
-+import org.slf4j.Logger;
-+
-+import java.io.IOException;
-+import java.util.concurrent.CompletableFuture;
-+import java.util.concurrent.ConcurrentHashMap;
-+import java.util.concurrent.atomic.AtomicLong;
-+import java.util.function.Consumer;
-+import java.util.function.Function;
-+
-+/**
-+ * Prioritized singleton thread responsible for all chunk IO that occurs in a minecraft server.
-+ *
-+ * <p>
-+ * Singleton access: {@link Holder#INSTANCE}
-+ * </p>
-+ *
-+ * <p>
-+ * All functions provided are MT-Safe, however certain ordering constraints are (but not enforced):
-+ * <li>
-+ * Chunk saves may not occur for unloaded chunks.
-+ * </li>
-+ * <li>
-+ * Tasks must be scheduled on the main thread.
-+ * </li>
-+ * </p>
-+ *
-+ * @see Holder#INSTANCE
-+ * @see #scheduleSave(ServerLevel, int, int, CompoundTag, CompoundTag, int)
-+ * @see #loadChunkDataAsync(ServerLevel, int, int, int, Consumer, boolean, boolean, boolean)
-+ */
-+public final class PaperFileIOThread extends QueueExecutorThread {
-+
-+ public static final Logger LOGGER = LogUtils.getLogger();
-+ public static final CompoundTag FAILURE_VALUE = new CompoundTag();
-+
-+ public static final class Holder {
-+
-+ public static final PaperFileIOThread INSTANCE = new PaperFileIOThread();
-+
-+ static {
-+ INSTANCE.start();
-+ }
-+ }
-+
-+ private final AtomicLong writeCounter = new AtomicLong();
-+
-+ private PaperFileIOThread() {
-+ super(new PrioritizedTaskQueue<>(), (int)(1.0e6)); // 1.0ms spinwait time
-+ this.setName("Paper RegionFile IO Thread");
-+ this.setPriority(Thread.NORM_PRIORITY - 1); // we keep priority close to normal because threads can wait on us
-+ this.setUncaughtExceptionHandler((final Thread unused, final Throwable thr) -> {
-+ LOGGER.error("Uncaught exception thrown from IO thread, report this!", thr);
-+ });
-+ }
-+
-+ /* run() is implemented by superclass */
-+
-+ /*
-+ *
-+ * IO thread will perform reads before writes
-+ *
-+ * How reads/writes are scheduled:
-+ *
-+ * If read in progress while scheduling write, ignore read and schedule write
-+ * If read in progress while scheduling read (no write in progress), chain the read task
-+ *
-+ *
-+ * If write in progress while scheduling read, use the pending write data and ret immediately
-+ * If write in progress while scheduling write (ignore read in progress), overwrite the write in progress data
-+ *
-+ * This allows the reads and writes to act as if they occur synchronously to the thread scheduling them, however
-+ * it fails to properly propagate write failures. When writes fail the data is kept so future reads will actually
-+ * read the failed write data. This should hopefully act as a way to prevent data loss for spurious fails for writing data.
-+ *
-+ */
-+
-+ /**
-+ * Attempts to bump the priority of all IO tasks for the given chunk coordinates. This has no effect if no tasks are queued.
-+ * @param world Chunk's world
-+ * @param chunkX Chunk's x coordinate
-+ * @param chunkZ Chunk's z coordinate
-+ * @param priority Priority level to try to bump to
-+ */
-+ public void bumpPriority(final ServerLevel world, final int chunkX, final int chunkZ, final int priority) {
-+ if (!PrioritizedTaskQueue.validPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority: " + priority);
-+ }
-+
-+ final Long key = Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ));
-+
-+ final ChunkDataTask poiTask = world.poiDataController.tasks.get(key);
-+ final ChunkDataTask chunkTask = world.chunkDataController.tasks.get(key);
-+
-+ if (poiTask != null) {
-+ poiTask.raisePriority(priority);
-+ }
-+ if (chunkTask != null) {
-+ chunkTask.raisePriority(priority);
-+ }
-+ }
-+
-+ public CompoundTag getPendingWrite(final ServerLevel world, final int chunkX, final int chunkZ, final boolean poiData) {
-+ final ChunkDataController taskController = poiData ? world.poiDataController : world.chunkDataController;
-+
-+ final ChunkDataTask dataTask = taskController.tasks.get(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)));
-+
-+ if (dataTask == null) {
-+ return null;
-+ }
-+
-+ final ChunkDataController.InProgressWrite write = dataTask.inProgressWrite;
-+
-+ if (write == null) {
-+ return null;
-+ }
-+
-+ return write.data;
-+ }
-+
-+ /**
-+ * Sets the priority of all IO tasks for the given chunk coordinates. This has no effect if no tasks are queued.
-+ * @param world Chunk's world
-+ * @param chunkX Chunk's x coordinate
-+ * @param chunkZ Chunk's z coordinate
-+ * @param priority Priority level to set to
-+ */
-+ public void setPriority(final ServerLevel world, final int chunkX, final int chunkZ, final int priority) {
-+ if (!PrioritizedTaskQueue.validPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority: " + priority);
-+ }
-+
-+ final Long key = Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ));
-+
-+ final ChunkDataTask poiTask = world.poiDataController.tasks.get(key);
-+ final ChunkDataTask chunkTask = world.chunkDataController.tasks.get(key);
-+
-+ if (poiTask != null) {
-+ poiTask.updatePriority(priority);
-+ }
-+ if (chunkTask != null) {
-+ chunkTask.updatePriority(priority);
-+ }
-+ }
-+
-+ /**
-+ * Schedules the chunk data to be written asynchronously.
-+ * <p>
-+ * Impl notes:
-+ * </p>
-+ * <li>
-+ * This function presumes a chunk load for the coordinates is not called during this function (anytime after is OK). This means
-+ * saves must be scheduled before a chunk is unloaded.
-+ * </li>
-+ * <li>
-+ * Writes may be called concurrently, although only the "later" write will go through.
-+ * </li>
-+ * @param world Chunk's world
-+ * @param chunkX Chunk's x coordinate
-+ * @param chunkZ Chunk's z coordinate
-+ * @param poiData Chunk point of interest data. If {@code null}, then no poi data is saved.
-+ * @param chunkData Chunk data. If {@code null}, then no chunk data is saved.
-+ * @param priority Priority level for this task. See {@link PrioritizedTaskQueue}
-+ * @throws IllegalArgumentException If both {@code poiData} and {@code chunkData} are {@code null}.
-+ * @throws IllegalStateException If the file io thread has shutdown.
-+ */
-+ public void scheduleSave(final ServerLevel world, final int chunkX, final int chunkZ,
-+ final CompoundTag poiData, final CompoundTag chunkData,
-+ final int priority) throws IllegalArgumentException {
-+ if (!PrioritizedTaskQueue.validPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority: " + priority);
-+ }
-+
-+ final long writeCounter = this.writeCounter.getAndIncrement();
-+
-+ if (poiData != null) {
-+ this.scheduleWrite(world.poiDataController, world, chunkX, chunkZ, poiData, priority, writeCounter);
-+ }
-+ if (chunkData != null) {
-+ this.scheduleWrite(world.chunkDataController, world, chunkX, chunkZ, chunkData, priority, writeCounter);
-+ }
-+ }
-+
-+ private void scheduleWrite(final ChunkDataController dataController, final ServerLevel world,
-+ final int chunkX, final int chunkZ, final CompoundTag data, final int priority, final long writeCounter) {
-+ dataController.tasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkDataTask taskRunning) -> {
-+ if (taskRunning == null) {
-+ // no task is scheduled
-+
-+ // create task
-+ final ChunkDataTask newTask = new ChunkDataTask(priority, world, chunkX, chunkZ, dataController);
-+ newTask.inProgressWrite = new ChunkDataController.InProgressWrite();
-+ newTask.inProgressWrite.writeCounter = writeCounter;
-+ newTask.inProgressWrite.data = data;
-+
-+ PaperFileIOThread.this.queueTask(newTask); // schedule
-+ return newTask;
-+ }
-+
-+ taskRunning.raisePriority(priority);
-+
-+ if (taskRunning.inProgressWrite == null) {
-+ taskRunning.inProgressWrite = new ChunkDataController.InProgressWrite();
-+ }
-+
-+ boolean reschedule = taskRunning.inProgressWrite.writeCounter == -1L;
-+
-+ // synchronize for readers
-+ //noinspection SynchronizationOnLocalVariableOrMethodParameter
-+ synchronized (taskRunning) {
-+ taskRunning.inProgressWrite.data = data;
-+ taskRunning.inProgressWrite.writeCounter = writeCounter;
-+ }
-+
-+ if (reschedule) {
-+ // We need to reschedule this task since the previous one is not currently scheduled since it failed
-+ taskRunning.reschedule(priority);
-+ }
-+
-+ return taskRunning;
-+ });
-+ }
-+
-+ /**
-+ * Same as {@link #loadChunkDataAsync(ServerLevel, int, int, int, Consumer, boolean, boolean, boolean)}, except this function returns
-+ * a {@link CompletableFuture} which is potentially completed <b>ASYNCHRONOUSLY ON THE FILE IO THREAD</b> when the load task
-+ * has completed.
-+ * <p>
-+ * Note that if the chunk fails to load the returned future is completed with {@code null}.
-+ * </p>
-+ */
-+ public CompletableFuture<ChunkData> loadChunkDataAsyncFuture(final ServerLevel world, final int chunkX, final int chunkZ,
-+ final int priority, final boolean readPoiData, final boolean readChunkData,
-+ final boolean intendingToBlock) {
-+ final CompletableFuture<ChunkData> future = new CompletableFuture<>();
-+ this.loadChunkDataAsync(world, chunkX, chunkZ, priority, future::complete, readPoiData, readChunkData, intendingToBlock);
-+ return future;
-+ }
-+
-+ /**
-+ * Schedules a load to be executed asynchronously.
-+ * <p>
-+ * Impl notes:
-+ * </p>
-+ * <li>
-+ * If a chunk fails to load, the {@code onComplete} parameter is completed with {@code null}.
-+ * </li>
-+ * <li>
-+ * It is possible for the {@code onComplete} parameter to be given {@link ChunkData} containing data
-+ * this call did not request.
-+ * </li>
-+ * <li>
-+ * The {@code onComplete} parameter may be completed during the execution of this function synchronously or it may
-+ * be completed asynchronously on this file io thread. Interacting with the file IO thread in the completion of
-+ * data is undefined behaviour, and can cause deadlock.
-+ * </li>
-+ * @param world Chunk's world
-+ * @param chunkX Chunk's x coordinate
-+ * @param chunkZ Chunk's z coordinate
-+ * @param priority Priority level for this task. See {@link PrioritizedTaskQueue}
-+ * @param onComplete Consumer to execute once this task has completed
-+ * @param readPoiData Whether to read point of interest data. If {@code false}, the {@code NBTTagCompound} will be {@code null}.
-+ * @param readChunkData Whether to read chunk data. If {@code false}, the {@code NBTTagCompound} will be {@code null}.
-+ * @return The {@link PrioritizedTaskQueue.PrioritizedTask} associated with this task. Note that this task does not support
-+ * cancellation.
-+ */
-+ public void loadChunkDataAsync(final ServerLevel world, final int chunkX, final int chunkZ,
-+ final int priority, final Consumer<ChunkData> onComplete,
-+ final boolean readPoiData, final boolean readChunkData,
-+ final boolean intendingToBlock) {
-+ if (!PrioritizedTaskQueue.validPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority: " + priority);
-+ }
-+
-+ if (!(readPoiData | readChunkData)) {
-+ throw new IllegalArgumentException("Must read chunk data or poi data");
-+ }
-+
-+ final ChunkData complete = new ChunkData();
-+ final boolean[] requireCompletion = new boolean[] { readPoiData, readChunkData };
-+
-+ if (readPoiData) {
-+ this.scheduleRead(world.poiDataController, world, chunkX, chunkZ, (final CompoundTag poiData) -> {
-+ complete.poiData = poiData;
-+
-+ final boolean finished;
-+
-+ // avoid a race condition where the file io thread completes and we complete synchronously
-+ // Note: Synchronization can be elided if both of the accesses are volatile
-+ synchronized (requireCompletion) {
-+ requireCompletion[0] = false; // 0 -> poi data
-+ finished = !requireCompletion[1]; // 1 -> chunk data
-+ }
-+
-+ if (finished) {
-+ onComplete.accept(complete);
-+ }
-+ }, priority, intendingToBlock);
-+ }
-+
-+ if (readChunkData) {
-+ this.scheduleRead(world.chunkDataController, world, chunkX, chunkZ, (final CompoundTag chunkData) -> {
-+ complete.chunkData = chunkData;
-+
-+ final boolean finished;
-+
-+ // avoid a race condition where the file io thread completes and we complete synchronously
-+ // Note: Synchronization can be elided if both of the accesses are volatile
-+ synchronized (requireCompletion) {
-+ requireCompletion[1] = false; // 1 -> chunk data
-+ finished = !requireCompletion[0]; // 0 -> poi data
-+ }
-+
-+ if (finished) {
-+ onComplete.accept(complete);
-+ }
-+ }, priority, intendingToBlock);
-+ }
-+
-+ }
-+
-+ // Note: the onComplete may be called asynchronously or synchronously here.
-+ private void scheduleRead(final ChunkDataController dataController, final ServerLevel world,
-+ final int chunkX, final int chunkZ, final Consumer<CompoundTag> onComplete, final int priority,
-+ final boolean intendingToBlock) {
-+
-+ Function<RegionFile, Boolean> tryLoadFunction = (final RegionFile file) -> {
-+ if (file == null) {
-+ return Boolean.TRUE;
-+ }
-+ return Boolean.valueOf(file.hasChunk(new ChunkPos(chunkX, chunkZ)));
-+ };
-+
-+ dataController.tasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkDataTask running) -> {
-+ if (running == null) {
-+ // not scheduled
-+
-+ final Boolean shouldSchedule = intendingToBlock ? dataController.computeForRegionFile(chunkX, chunkZ, tryLoadFunction) :
-+ dataController.computeForRegionFileIfLoaded(chunkX, chunkZ, tryLoadFunction);
-+
-+ if (shouldSchedule == Boolean.FALSE) {
-+ // not on disk
-+ onComplete.accept(null);
-+ return null;
-+ }
-+
-+ // set up task
-+ final ChunkDataTask newTask = new ChunkDataTask(priority, world, chunkX, chunkZ, dataController);
-+ newTask.inProgressRead = new ChunkDataController.InProgressRead();
-+ newTask.inProgressRead.readFuture.thenAccept(onComplete);
-+
-+ PaperFileIOThread.this.queueTask(newTask); // schedule task
-+ return newTask;
-+ }
-+
-+ running.raisePriority(priority);
-+
-+ if (running.inProgressWrite == null) {
-+ // chain to the read future
-+ running.inProgressRead.readFuture.thenAccept(onComplete);
-+ return running;
-+ }
-+
-+ // at this stage we have to use the in progress write's data to avoid an order issue
-+ // we don't synchronize since all writes to data occur in the compute() call
-+ onComplete.accept(running.inProgressWrite.data);
-+ return running;
-+ });
-+ }
-+
-+ /**
-+ * Same as {@link #loadChunkDataAsync(ServerLevel, int, int, int, Consumer, boolean, boolean, boolean)}, except this function returns
-+ * the {@link ChunkData} associated with the specified chunk when the task is complete.
-+ * @return The chunk data, or {@code null} if the chunk failed to load.
-+ */
-+ public ChunkData loadChunkData(final ServerLevel world, final int chunkX, final int chunkZ, final int priority,
-+ final boolean readPoiData, final boolean readChunkData) {
-+ return this.loadChunkDataAsyncFuture(world, chunkX, chunkZ, priority, readPoiData, readChunkData, true).join();
-+ }
-+
-+ /**
-+ * Schedules the given task at the specified priority to be executed on the IO thread.
-+ * <p>
-+ * Internal api. Do not use.
-+ * </p>
-+ */
-+ public void runTask(final int priority, final Runnable runnable) {
-+ this.queueTask(new GeneralTask(priority, runnable));
-+ }
-+
-+ static final class GeneralTask extends PrioritizedTaskQueue.PrioritizedTask implements Runnable {
-+
-+ private final Runnable run;
-+
-+ public GeneralTask(final int priority, final Runnable run) {
-+ super(priority);
-+ this.run = IOUtil.notNull(run, "Task may not be null");
-+ }
-+
-+ @Override
-+ public void run() {
-+ try {
-+ this.run.run();
-+ } catch (final Throwable throwable) {
-+ if (throwable instanceof ThreadDeath) {
-+ throw (ThreadDeath)throwable;
-+ }
-+ LOGGER.error("Failed to execute general task on IO thread " + IOUtil.genericToString(this.run), throwable);
-+ }
-+ }
-+ }
-+
-+ public static final class ChunkData {
-+
-+ public CompoundTag poiData;
-+ public CompoundTag chunkData;
-+
-+ public ChunkData() {}
-+
-+ public ChunkData(final CompoundTag poiData, final CompoundTag chunkData) {
-+ this.poiData = poiData;
-+ this.chunkData = chunkData;
-+ }
-+ }
-+
-+ public static abstract class ChunkDataController {
-+
-+ // ConcurrentHashMap synchronizes per chain, so reduce the chance of task's hashes colliding.
-+ public final ConcurrentHashMap<Long, ChunkDataTask> tasks = new ConcurrentHashMap<>(64, 0.5f);
-+
-+ public abstract void writeData(final int x, final int z, final CompoundTag compound) throws IOException;
-+ public abstract CompoundTag readData(final int x, final int z) throws IOException;
-+
-+ public abstract <T> T computeForRegionFile(final int chunkX, final int chunkZ, final Function<RegionFile, T> function);
-+ public abstract <T> T computeForRegionFileIfLoaded(final int chunkX, final int chunkZ, final Function<RegionFile, T> function);
-+
-+ public static final class InProgressWrite {
-+ public long writeCounter;
-+ public CompoundTag data;
-+ }
-+
-+ public static final class InProgressRead {
-+ public final CompletableFuture<CompoundTag> readFuture = new CompletableFuture<>();
-+ }
-+ }
-+
-+ public static final class ChunkDataTask extends PrioritizedTaskQueue.PrioritizedTask implements Runnable {
-+
-+ public ChunkDataController.InProgressWrite inProgressWrite;
-+ public ChunkDataController.InProgressRead inProgressRead;
-+
-+ private final ServerLevel world;
-+ private final int x;
-+ private final int z;
-+ private final ChunkDataController taskController;
-+
-+ public ChunkDataTask(final int priority, final ServerLevel world, final int x, final int z, final ChunkDataController taskController) {
-+ super(priority);
-+ this.world = world;
-+ this.x = x;
-+ this.z = z;
-+ this.taskController = taskController;
-+ }
-+
-+ @Override
-+ public String toString() {
-+ return "Task for world: '" + this.world.getWorld().getName() + "' at " + this.x + "," + this.z +
-+ " poi: " + (this.taskController == this.world.poiDataController) + ", hash: " + this.hashCode();
-+ }
-+
-+ /*
-+ *
-+ * IO thread will perform reads before writes
-+ *
-+ * How reads/writes are scheduled:
-+ *
-+ * If read in progress while scheduling write, ignore read and schedule write
-+ * If read in progress while scheduling read (no write in progress), chain the read task
-+ *
-+ *
-+ * If write in progress while scheduling read, use the pending write data and ret immediately
-+ * If write in progress while scheduling write (ignore read in progress), overwrite the write in progress data
-+ *
-+ * This allows the reads and writes to act as if they occur synchronously to the thread scheduling them, however
-+ * it fails to properly propagate write failures
-+ *
-+ */
-+
-+ void reschedule(final int priority) {
-+ // priority is checked before this stage // TODO what
-+ this.queue.lazySet(null);
-+ this.priority.lazySet(priority);
-+ PaperFileIOThread.Holder.INSTANCE.queueTask(this);
-+ }
-+
-+ @Override
-+ public void run() {
-+ ChunkDataController.InProgressRead read = this.inProgressRead;
-+ if (read != null) {
-+ CompoundTag compound = PaperFileIOThread.FAILURE_VALUE;
-+ try {
-+ compound = this.taskController.readData(this.x, this.z);
-+ } catch (final Throwable thr) {
-+ if (thr instanceof ThreadDeath) {
-+ throw (ThreadDeath)thr;
-+ }
-+ LOGGER.error("Failed to read chunk data for task: " + this.toString(), thr);
-+ // fall through to complete with null data
-+ }
-+ read.readFuture.complete(compound);
-+ }
-+
-+ final Long chunkKey = Long.valueOf(IOUtil.getCoordinateKey(this.x, this.z));
-+
-+ ChunkDataController.InProgressWrite write = this.inProgressWrite;
-+
-+ if (write == null) {
-+ // IntelliJ warns this is invalid, however it does not consider that writes to the task map & the inProgress field can occur concurrently.
-+ ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final Long keyInMap, final ChunkDataTask valueInMap) -> {
-+ if (valueInMap == null) {
-+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!");
-+ }
-+ if (valueInMap != ChunkDataTask.this) {
-+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!");
-+ }
-+ return valueInMap.inProgressWrite == null ? null : valueInMap;
-+ });
-+
-+ if (inMap == null) {
-+ return; // set the task value to null, indicating we're done
-+ }
-+
-+ // not null, which means there was a concurrent write
-+ write = this.inProgressWrite;
-+ }
-+
-+ for (;;) {
-+ final long writeCounter;
-+ final CompoundTag data;
-+
-+ //noinspection SynchronizationOnLocalVariableOrMethodParameter
-+ synchronized (write) {
-+ writeCounter = write.writeCounter;
-+ data = write.data;
-+ }
-+
-+ boolean failedWrite = false;
-+
-+ try {
-+ this.taskController.writeData(this.x, this.z, data);
-+ } catch (final Throwable thr) {
-+ if (thr instanceof ThreadDeath) {
-+ throw (ThreadDeath)thr;
-+ }
-+ LOGGER.error("Failed to write chunk data for task: " + this.toString(), thr);
-+ failedWrite = true;
-+ }
-+
-+ boolean finalFailWrite = failedWrite;
-+
-+ ChunkDataTask inMap = this.taskController.tasks.compute(chunkKey, (final Long keyInMap, final ChunkDataTask valueInMap) -> {
-+ if (valueInMap == null) {
-+ throw new IllegalStateException("Write completed concurrently, expected this task: " + ChunkDataTask.this.toString() + ", report this!");
-+ }
-+ if (valueInMap != ChunkDataTask.this) {
-+ throw new IllegalStateException("Chunk task mismatch, expected this task: " + ChunkDataTask.this.toString() + ", got: " + valueInMap.toString() + ", report this!");
-+ }
-+ if (valueInMap.inProgressWrite.writeCounter == writeCounter) {
-+ if (finalFailWrite) {
-+ valueInMap.inProgressWrite.writeCounter = -1L;
-+ }
-+
-+ return null;
-+ }
-+ return valueInMap;
-+ // Hack end
-+ });
-+
-+ if (inMap == null) {
-+ // write counter matched, so we wrote the most up-to-date pending data, we're done here
-+ // or we failed to write and successfully set the write counter to -1
-+ return; // we're done here
-+ }
-+
-+ // fetch & write new data
-+ continue;
-+ }
-+ }
-+ }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java b/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..24fe40c14cc50f8357a9c7a7493140fdea016a3d
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/io/PrioritizedTaskQueue.java
-@@ -0,0 +1,298 @@
-+package com.destroystokyo.paper.io;
-+
-+import java.util.concurrent.ConcurrentLinkedQueue;
-+import java.util.concurrent.atomic.AtomicBoolean;
-+import java.util.concurrent.atomic.AtomicInteger;
-+import java.util.concurrent.atomic.AtomicReference;
-+
-+public class PrioritizedTaskQueue<T extends PrioritizedTaskQueue.PrioritizedTask> {
-+
-+ // lower numbers are a higher priority (except < 0)
-+ // higher priorities are always executed before lower priorities
-+
-+ /**
-+ * Priority value indicating the task has completed or is being completed.
-+ */
-+ public static final int COMPLETING_PRIORITY = -1;
-+
-+ /**
-+ * Highest priority, should only be used for main thread tasks or tasks that are blocking the main thread.
-+ */
-+ public static final int HIGHEST_PRIORITY = 0;
-+
-+ /**
-+ * Should be only used in an IO task so that chunk loads do not wait on other IO tasks.
-+ * This only exists because IO tasks are scheduled before chunk load tasks to decrease IO waiting times.
-+ */
-+ public static final int HIGHER_PRIORITY = 1;
-+
-+ /**
-+ * Should be used for scheduling chunk loads/generation that would increase response times to users.
-+ */
-+ public static final int HIGH_PRIORITY = 2;
-+
-+ /**
-+ * Default priority.
-+ */
-+ public static final int NORMAL_PRIORITY = 3;
-+
-+ /**
-+ * Use for tasks not at all critical and can potentially be delayed.
-+ */
-+ public static final int LOW_PRIORITY = 4;
-+
-+ /**
-+ * Use for tasks that should "eventually" execute.
-+ */
-+ public static final int LOWEST_PRIORITY = 5;
-+
-+ private static final int TOTAL_PRIORITIES = 6;
-+
-+ final ConcurrentLinkedQueue<T>[] queues = (ConcurrentLinkedQueue<T>[])new ConcurrentLinkedQueue[TOTAL_PRIORITIES];
-+
-+ private final AtomicBoolean shutdown = new AtomicBoolean();
-+
-+ {
-+ for (int i = 0; i < TOTAL_PRIORITIES; ++i) {
-+ this.queues[i] = new ConcurrentLinkedQueue<>();
-+ }
-+ }
-+
-+ /**
-+ * Returns whether the specified priority is valid
-+ */
-+ public static boolean validPriority(final int priority) {
-+ return priority >= 0 && priority < TOTAL_PRIORITIES;
-+ }
-+
-+ /**
-+ * Queues a task.
-+ * @throws IllegalStateException If the task has already been queued. Use {@link PrioritizedTask#raisePriority(int)} to
-+ * raise a task's priority.
-+ * This can also be thrown if the queue has shutdown.
-+ */
-+ public void add(final T task) throws IllegalStateException {
-+ int priority = task.getPriority();
-+ if (priority != COMPLETING_PRIORITY) {
-+ task.setQueue(this);
-+ this.queues[priority].add(task);
-+ }
-+ if (this.shutdown.get()) {
-+ // note: we're not actually sure at this point if our task will go through
-+ throw new IllegalStateException("Queue has shutdown, refusing to execute task " + IOUtil.genericToString(task));
-+ }
-+ }
-+
-+ /**
-+ * Polls the highest priority task currently available. {@code null} if none.
-+ */
-+ public T poll() {
-+ T task;
-+ for (int i = 0; i < TOTAL_PRIORITIES; ++i) {
-+ final ConcurrentLinkedQueue<T> queue = this.queues[i];
-+
-+ while ((task = queue.poll()) != null) {
-+ final int prevPriority = task.tryComplete(i);
-+ if (prevPriority != COMPLETING_PRIORITY && prevPriority <= i) {
-+ // if the prev priority was greater-than or equal to our current priority
-+ return task;
-+ }
-+ }
-+ }
-+
-+ return null;
-+ }
-+
-+ /**
-+ * Polls the highest priority task currently available. {@code null} if none.
-+ */
-+ public T poll(final int lowestPriority) {
-+ T task;
-+ final int max = Math.min(LOWEST_PRIORITY, lowestPriority);
-+ for (int i = 0; i <= max; ++i) {
-+ final ConcurrentLinkedQueue<T> queue = this.queues[i];
-+
-+ while ((task = queue.poll()) != null) {
-+ final int prevPriority = task.tryComplete(i);
-+ if (prevPriority != COMPLETING_PRIORITY && prevPriority <= i) {
-+ // if the prev priority was greater-than or equal to our current priority
-+ return task;
-+ }
-+ }
-+ }
-+
-+ return null;
-+ }
-+
-+ /**
-+ * Returns whether this queue may have tasks queued.
-+ * <p>
-+ * This operation is not atomic, but is MT-Safe.
-+ * </p>
-+ * @return {@code true} if tasks may be queued, {@code false} otherwise
-+ */
-+ public boolean hasTasks() {
-+ for (int i = 0; i < TOTAL_PRIORITIES; ++i) {
-+ final ConcurrentLinkedQueue<T> queue = this.queues[i];
-+
-+ if (queue.peek() != null) {
-+ return true;
-+ }
-+ }
-+ return false;
-+ }
-+
-+ /**
-+ * Prevent further additions to this queue. Attempts to add after this call has completed (potentially during) will
-+ * result in {@link IllegalStateException} being thrown.
-+ * <p>
-+ * This operation is atomic with respect to other shutdown calls
-+ * </p>
-+ * <p>
-+ * After this call has completed, regardless of return value, this queue will be shutdown.
-+ * </p>
-+ * @return {@code true} if the queue was shutdown, {@code false} if it has shut down already
-+ */
-+ public boolean shutdown() {
-+ return this.shutdown.getAndSet(false);
-+ }
-+
-+ public abstract static class PrioritizedTask {
-+
-+ protected final AtomicReference<PrioritizedTaskQueue> queue = new AtomicReference<>();
-+
-+ protected final AtomicInteger priority;
-+
-+ protected PrioritizedTask() {
-+ this(PrioritizedTaskQueue.NORMAL_PRIORITY);
-+ }
-+
-+ protected PrioritizedTask(final int priority) {
-+ if (!PrioritizedTaskQueue.validPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority " + priority);
-+ }
-+ this.priority = new AtomicInteger(priority);
-+ }
-+
-+ /**
-+ * Returns the current priority. Note that {@link PrioritizedTaskQueue#COMPLETING_PRIORITY} will be returned
-+ * if this task is completing or has completed.
-+ */
-+ public final int getPriority() {
-+ return this.priority.get();
-+ }
-+
-+ /**
-+ * Returns whether this task is scheduled to execute, or has been already executed.
-+ */
-+ public boolean isScheduled() {
-+ return this.queue.get() != null;
-+ }
-+
-+ final int tryComplete(final int minPriority) {
-+ for (int curr = this.getPriorityVolatile();;) {
-+ if (curr == COMPLETING_PRIORITY) {
-+ return COMPLETING_PRIORITY;
-+ }
-+ if (curr > minPriority) {
-+ // curr is lower priority
-+ return curr;
-+ }
-+
-+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, COMPLETING_PRIORITY))) {
-+ return curr;
-+ }
-+ continue;
-+ }
-+ }
-+
-+ /**
-+ * Forces this task to be completed.
-+ * @return {@code true} if the task was cancelled, {@code false} if the task has already completed or is being completed.
-+ */
-+ public boolean cancel() {
-+ return this.exchangePriorityVolatile(PrioritizedTaskQueue.COMPLETING_PRIORITY) != PrioritizedTaskQueue.COMPLETING_PRIORITY;
-+ }
-+
-+ /**
-+ * Attempts to raise the priority to the priority level specified.
-+ * @param priority Priority specified
-+ * @return {@code true} if successful, {@code false} otherwise.
-+ */
-+ public boolean raisePriority(final int priority) {
-+ if (!PrioritizedTaskQueue.validPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority");
-+ }
-+
-+ for (int curr = this.getPriorityVolatile();;) {
-+ if (curr == COMPLETING_PRIORITY) {
-+ return false;
-+ }
-+ if (priority >= curr) {
-+ return true;
-+ }
-+
-+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority))) {
-+ PrioritizedTaskQueue queue = this.queue.get();
-+ if (queue != null) {
-+ //noinspection unchecked
-+ queue.queues[priority].add(this); // silently fail on shutdown
-+ }
-+ return true;
-+ }
-+ continue;
-+ }
-+ }
-+
-+ /**
-+ * Attempts to set this task's priority level to the level specified.
-+ * @param priority Specified priority level.
-+ * @return {@code true} if successful, {@code false} if this task is completing or has completed.
-+ */
-+ public boolean updatePriority(final int priority) {
-+ if (!PrioritizedTaskQueue.validPriority(priority)) {
-+ throw new IllegalArgumentException("Invalid priority");
-+ }
-+
-+ for (int curr = this.getPriorityVolatile();;) {
-+ if (curr == COMPLETING_PRIORITY) {
-+ return false;
-+ }
-+ if (curr == priority) {
-+ return true;
-+ }
-+
-+ if (curr == (curr = this.compareAndExchangePriorityVolatile(curr, priority))) {
-+ PrioritizedTaskQueue queue = this.queue.get();
-+ if (queue != null) {
-+ //noinspection unchecked
-+ queue.queues[priority].add(this); // silently fail on shutdown
-+ }
-+ return true;
-+ }
-+ continue;
-+ }
-+ }
-+
-+ void setQueue(final PrioritizedTaskQueue queue) {
-+ this.queue.set(queue);
-+ }
-+
-+ /* priority */
-+
-+ protected final int getPriorityVolatile() {
-+ return this.priority.get();
-+ }
-+
-+ protected final int compareAndExchangePriorityVolatile(final int expect, final int update) {
-+ if (this.priority.compareAndSet(expect, update)) {
-+ return expect;
-+ }
-+ return this.priority.get();
-+ }
-+
-+ protected final int exchangePriorityVolatile(final int value) {
-+ return this.priority.getAndSet(value);
-+ }
-+ }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java b/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..f1b940704400266e6df186139b57ec72ae314448
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/io/QueueExecutorThread.java
-@@ -0,0 +1,254 @@
-+package com.destroystokyo.paper.io;
-+
-+import com.mojang.logging.LogUtils;
-+import org.slf4j.Logger;
-+
-+import java.util.concurrent.ConcurrentLinkedQueue;
-+import java.util.concurrent.atomic.AtomicBoolean;
-+import java.util.concurrent.locks.LockSupport;
-+
-+public class QueueExecutorThread<T extends PrioritizedTaskQueue.PrioritizedTask & Runnable> extends Thread {
-+
-+ private static final Logger LOGGER = LogUtils.getLogger();
-+
-+ protected final PrioritizedTaskQueue<T> queue;
-+ protected final long spinWaitTime;
-+
-+ protected volatile boolean closed;
-+
-+ protected final AtomicBoolean parked = new AtomicBoolean();
-+
-+ protected volatile ConcurrentLinkedQueue<Thread> flushQueue = new ConcurrentLinkedQueue<>();
-+ protected volatile long flushCycles;
-+
-+ protected int lowestPriorityToPoll = PrioritizedTaskQueue.LOWEST_PRIORITY;
-+
-+ public int getLowestPriorityToPoll() {
-+ return this.lowestPriorityToPoll;
-+ }
-+
-+ public void setLowestPriorityToPoll(final int lowestPriorityToPoll) {
-+ if (this.isAlive()) {
-+ throw new IllegalStateException("Cannot set after starting");
-+ }
-+ this.lowestPriorityToPoll = lowestPriorityToPoll;
-+ }
-+
-+ public QueueExecutorThread(final PrioritizedTaskQueue<T> queue) {
-+ this(queue, (int)(1.e6)); // 1.0ms
-+ }
-+
-+ public QueueExecutorThread(final PrioritizedTaskQueue<T> queue, final long spinWaitTime) { // in ms
-+ this.queue = queue;
-+ this.spinWaitTime = spinWaitTime;
-+ }
-+
-+ @Override
-+ public void run() {
-+ final long spinWaitTime = this.spinWaitTime;
-+ main_loop:
-+ for (;;) {
-+ this.pollTasks(true);
-+
-+ // spinwait
-+
-+ final long start = System.nanoTime();
-+
-+ for (;;) {
-+ // If we are interrpted for any reason, park() will always return immediately. Clear so that we don't needlessly use cpu in such an event.
-+ Thread.interrupted();
-+ LockSupport.parkNanos("Spinwaiting on tasks", 1000L); // 1us
-+
-+ if (this.pollTasks(true)) {
-+ // restart loop, found tasks
-+ continue main_loop;
-+ }
-+
-+ if (this.handleClose()) {
-+ return; // we're done
-+ }
-+
-+ if ((System.nanoTime() - start) >= spinWaitTime) {
-+ break;
-+ }
-+ }
-+
-+ if (this.handleClose()) {
-+ return;
-+ }
-+
-+ this.parked.set(true);
-+
-+ // We need to parse here to avoid a race condition where a thread queues a task before we set parked to true
-+ // (i.e it will not notify us)
-+ if (this.pollTasks(true)) {
-+ this.parked.set(false);
-+ continue;
-+ }
-+
-+ if (this.handleClose()) {
-+ return;
-+ }
-+
-+ // we don't need to check parked before sleeping, but we do need to check parked in a do-while loop
-+ // LockSupport.park() can fail for any reason
-+ do {
-+ Thread.interrupted();
-+ LockSupport.park("Waiting on tasks");
-+ } while (this.parked.get());
-+ }
-+ }
-+
-+ protected boolean handleClose() {
-+ if (this.closed) {
-+ this.pollTasks(true); // this ensures we've emptied the queue
-+ this.handleFlushThreads(true);
-+ return true;
-+ }
-+ return false;
-+ }
-+
-+ protected boolean pollTasks(boolean flushTasks) {
-+ Runnable task;
-+ boolean ret = false;
-+
-+ while ((task = this.queue.poll(this.lowestPriorityToPoll)) != null) {
-+ ret = true;
-+ try {
-+ task.run();
-+ } catch (final Throwable throwable) {
-+ if (throwable instanceof ThreadDeath) {
-+ throw (ThreadDeath)throwable;
-+ }
-+ LOGGER.error("Exception thrown from prioritized runnable task in thread '" + this.getName() + "': " + IOUtil.genericToString(task), throwable);
-+ }
-+ }
-+
-+ if (flushTasks) {
-+ this.handleFlushThreads(false);
-+ }
-+
-+ return ret;
-+ }
-+
-+ protected void handleFlushThreads(final boolean shutdown) {
-+ Thread parking;
-+ ConcurrentLinkedQueue<Thread> flushQueue = this.flushQueue;
-+ do {
-+ ++flushCycles; // may be plain read opaque write
-+ while ((parking = flushQueue.poll()) != null) {
-+ LockSupport.unpark(parking);
-+ }
-+ } while (this.pollTasks(false));
-+
-+ if (shutdown) {
-+ this.flushQueue = null;
-+
-+ // defend against a race condition where a flush thread double-checks right before we set to null
-+ while ((parking = flushQueue.poll()) != null) {
-+ LockSupport.unpark(parking);
-+ }
-+ }
-+ }
-+
-+ /**
-+ * Notify's this thread that a task has been added to its queue
-+ * @return {@code true} if this thread was waiting for tasks, {@code false} if it is executing tasks
-+ */
-+ public boolean notifyTasks() {
-+ if (this.parked.get() && this.parked.getAndSet(false)) {
-+ LockSupport.unpark(this);
-+ return true;
-+ }
-+ return false;
-+ }
-+
-+ protected void queueTask(final T task) {
-+ this.queue.add(task);
-+ this.notifyTasks();
-+ }
-+
-+ /**
-+ * Waits until this thread's queue is empty.
-+ *
-+ * @throws IllegalStateException If the current thread is {@code this} thread.
-+ */
-+ public void flush() {
-+ final Thread currentThread = Thread.currentThread();
-+
-+ if (currentThread == this) {
-+ // avoid deadlock
-+ throw new IllegalStateException("Cannot flush the queue executor thread while on the queue executor thread");
-+ }
-+
-+ // order is important
-+
-+ int successes = 0;
-+ long lastCycle = -1L;
-+
-+ do {
-+ final ConcurrentLinkedQueue<Thread> flushQueue = this.flushQueue;
-+ if (flushQueue == null) {
-+ return;
-+ }
-+
-+ flushQueue.add(currentThread);
-+
-+ // double check flush queue
-+ if (this.flushQueue == null) {
-+ return;
-+ }
-+
-+ final long currentCycle = this.flushCycles; // may be opaque read
-+
-+ if (currentCycle == lastCycle) {
-+ Thread.yield();
-+ continue;
-+ }
-+
-+ // force response
-+ this.parked.set(false);
-+ LockSupport.unpark(this);
-+
-+ LockSupport.park("flushing queue executor thread");
-+
-+ // returns whether there are tasks queued, does not return whether there are tasks executing
-+ // this is why we cycle twice twice through flush (we know a pollTask call is made after a flush cycle)
-+ // we really only need to guarantee that the tasks this thread has queued has gone through, and can leave
-+ // tasks queued concurrently that are unsychronized with this thread as undefined behavior
-+ if (this.queue.hasTasks()) {
-+ successes = 0;
-+ } else {
-+ ++successes;
-+ }
-+
-+ } while (successes != 2);
-+
-+ }
-+
-+ /**
-+ * Closes this queue executor's queue and optionally waits for it to empty.
-+ * <p>
-+ * If wait is {@code true}, then the queue will be empty by the time this call completes.
-+ * </p>
-+ * <p>
-+ * This function is MT-Safe.
-+ * </p>
-+ * @param wait If this call is to wait until the queue is empty
-+ * @param killQueue Whether to shutdown this thread's queue
-+ * @return whether this thread shut down the queue
-+ */
-+ public boolean close(final boolean wait, final boolean killQueue) {
-+ boolean ret = !killQueue ? false : this.queue.shutdown();
-+ this.closed = true;
-+
-+ // force thread to respond to the shutdown
-+ this.parked.set(false);
-+ LockSupport.unpark(this);
-+
-+ if (wait) {
-+ this.flush();
-+ }
-+ return ret;
-+ }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkLoadTask.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkLoadTask.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..e9070b6158e7e8c2dd33a9dcb20898a2f0d86e48
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkLoadTask.java
-@@ -0,0 +1,148 @@
-+package com.destroystokyo.paper.io.chunk;
-+
-+import co.aikar.timings.Timing;
-+import com.destroystokyo.paper.io.PaperFileIOThread;
-+import com.destroystokyo.paper.io.IOUtil;
-+import java.util.ArrayDeque;
-+import java.util.function.Consumer;
-+import com.mojang.logging.LogUtils;
-+import net.minecraft.server.level.ChunkMap;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.world.level.ChunkPos;
-+import net.minecraft.world.level.chunk.storage.ChunkSerializer;
-+import org.slf4j.Logger;
-+
-+public final class ChunkLoadTask extends ChunkTask {
-+
-+ private static final Logger LOGGER = LogUtils.getLogger();
-+
-+ public boolean cancelled;
-+
-+ Consumer<ChunkSerializer.InProgressChunkHolder> onComplete;
-+ public PaperFileIOThread.ChunkData chunkData;
-+
-+ private boolean hasCompleted;
-+
-+ public ChunkLoadTask(final ServerLevel world, final int chunkX, final int chunkZ, final int priority,
-+ final ChunkTaskManager taskManager,
-+ final Consumer<ChunkSerializer.InProgressChunkHolder> onComplete) {
-+ super(world, chunkX, chunkZ, priority, taskManager);
-+ this.onComplete = onComplete;
-+ }
-+
-+ private static final ArrayDeque<Runnable> EMPTY_QUEUE = new ArrayDeque<>();
-+
-+ private static ChunkSerializer.InProgressChunkHolder createEmptyHolder() {
-+ return new ChunkSerializer.InProgressChunkHolder(null, EMPTY_QUEUE);
-+ }
-+
-+ @Override
-+ public void run() {
-+ try {
-+ this.executeTask();
-+ } catch (final Throwable ex) {
-+ LOGGER.error("Failed to execute chunk load task: " + this.toString(), ex);
-+ if (!this.hasCompleted) {
-+ this.complete(ChunkLoadTask.createEmptyHolder());
-+ }
-+ }
-+ }
-+
-+ private boolean checkCancelled() {
-+ if (this.cancelled) {
-+ // IntelliJ does not understand writes may occur to cancelled concurrently.
-+ return this.taskManager.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(this.chunkX, this.chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> {
-+ if (valueInMap != ChunkLoadTask.this) {
-+ throw new IllegalStateException("Expected this task to be scheduled, but another was! Other: " + valueInMap + ", current: " + ChunkLoadTask.this);
-+ }
-+
-+ if (valueInMap.cancelled) {
-+ return null;
-+ }
-+ return valueInMap;
-+ }) == null;
-+ }
-+ return false;
-+ }
-+
-+ public void executeTask() {
-+ if (this.checkCancelled()) {
-+ return;
-+ }
-+
-+ // either executed synchronously or asynchronously
-+ final PaperFileIOThread.ChunkData chunkData = this.chunkData;
-+
-+ if (chunkData.poiData == PaperFileIOThread.FAILURE_VALUE || chunkData.chunkData == PaperFileIOThread.FAILURE_VALUE) {
-+ LOGGER.error("Could not load chunk for task: " + this.toString() + ", file IO thread has dumped the relevant exception above");
-+ this.complete(ChunkLoadTask.createEmptyHolder());
-+ return;
-+ }
-+
-+ if (chunkData.chunkData == null) {
-+ // not on disk
-+ this.complete(ChunkLoadTask.createEmptyHolder());
-+ return;
-+ }
-+
-+ final ChunkPos chunkPos = new ChunkPos(this.chunkX, this.chunkZ);
-+
-+ final ChunkMap chunkManager = this.world.getChunkSource().chunkMap;
-+
-+ try (Timing ignored = this.world.timings.chunkLoadLevelTimer.startTimingIfSync()) {
-+ final ChunkSerializer.InProgressChunkHolder chunkHolder;
-+
-+ // apply fixes
-+
-+ try {
-+ chunkData.chunkData = chunkManager.upgradeChunkTag(this.world.getTypeKey(),
-+ chunkManager.overworldDataStorage, chunkData.chunkData, chunkManager.generator.getTypeNameForDataFixer(), chunkPos, this.world); // clone data for safety, file IO thread does not clone
-+ } catch (final Throwable ex) {
-+ LOGGER.error("Could not apply datafixers for chunk task: " + this.toString(), ex);
-+ this.complete(ChunkLoadTask.createEmptyHolder());
-+ return;
-+ }
-+
-+ if (!ChunkMap.isChunkDataValid(chunkData.chunkData)) {
-+ LOGGER.error("Chunk file at {} is missing level data, skipping", new ChunkPos(this.chunkX, this.chunkZ));
-+ this.complete(ChunkLoadTask.createEmptyHolder());
-+ return;
-+ }
-+
-+ if (this.checkCancelled()) {
-+ return;
-+ }
-+
-+ try {
-+ chunkHolder = ChunkSerializer.loadChunk(this.world, chunkManager.getPoiManager(), chunkPos,
-+ chunkData.chunkData, true);
-+ } catch (final Throwable ex) {
-+ LOGGER.error("Could not de-serialize chunk data for task: " + this.toString(), ex);
-+ this.complete(ChunkLoadTask.createEmptyHolder());
-+ return;
-+ }
-+
-+ this.complete(chunkHolder);
-+ }
-+ }
-+
-+ private void complete(final ChunkSerializer.InProgressChunkHolder holder) {
-+ this.hasCompleted = true;
-+ holder.poiData = this.chunkData == null ? null : this.chunkData.poiData;
-+
-+ this.taskManager.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(this.chunkX, this.chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> {
-+ if (valueInMap != ChunkLoadTask.this) {
-+ throw new IllegalStateException("Expected this task to be scheduled, but another was! Other: " + valueInMap + ", current: " + ChunkLoadTask.this);
-+ }
-+ if (valueInMap.cancelled) {
-+ return null;
-+ }
-+ try {
-+ ChunkLoadTask.this.onComplete.accept(holder);
-+ } catch (final Throwable thr) {
-+ LOGGER.error("Failed to complete chunk data for task: " + this.toString(), thr);
-+ }
-+ return null;
-+ });
-+ }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkSaveTask.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkSaveTask.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..0d245ad7d19b11e946e0b5b43bf2181292297210
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkSaveTask.java
-@@ -0,0 +1,111 @@
-+package com.destroystokyo.paper.io.chunk;
-+
-+import co.aikar.timings.Timing;
-+import com.destroystokyo.paper.io.PaperFileIOThread;
-+import com.destroystokyo.paper.io.IOUtil;
-+import com.destroystokyo.paper.io.PrioritizedTaskQueue;
-+
-+import java.util.concurrent.CompletableFuture;
-+import java.util.concurrent.atomic.AtomicInteger;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.world.level.chunk.ChunkAccess;
-+import net.minecraft.world.level.chunk.storage.ChunkSerializer;
-+
-+public final class ChunkSaveTask extends ChunkTask {
-+
-+ public final ChunkSerializer.AsyncSaveData asyncSaveData;
-+ public final ChunkAccess chunk;
-+ public final CompletableFuture<CompoundTag> onComplete = new CompletableFuture<>();
-+
-+ private final AtomicInteger attemptedPriority;
-+
-+ public ChunkSaveTask(final ServerLevel world, final int chunkX, final int chunkZ, final int priority,
-+ final ChunkTaskManager taskManager, final ChunkSerializer.AsyncSaveData asyncSaveData,
-+ final ChunkAccess chunk) {
-+ super(world, chunkX, chunkZ, priority, taskManager);
-+ this.chunk = chunk;
-+ this.asyncSaveData = asyncSaveData;
-+ this.attemptedPriority = new AtomicInteger(priority);
-+ }
-+
-+ @Override
-+ public void run() {
-+ // can be executed asynchronously or synchronously
-+ final CompoundTag compound;
-+
-+ try (Timing ignored = this.world.timings.chunkUnloadDataSave.startTimingIfSync()) {
-+ compound = ChunkSerializer.saveChunk(this.world, this.chunk, this.asyncSaveData);
-+ } catch (final Throwable ex) {
-+ // has a plugin modified something it should not have and made us CME?
-+ PaperFileIOThread.LOGGER.error("Failed to serialize unloading chunk data for task: " + this.toString() + ", falling back to a synchronous execution", ex);
-+
-+ // Note: We add to the server thread queue here since this is what the server will drain tasks from
-+ // when waiting for chunks
-+ ChunkTaskManager.queueChunkWaitTask(() -> {
-+ try (Timing ignored = this.world.timings.chunkUnloadDataSave.startTiming()) {
-+ CompoundTag data = PaperFileIOThread.FAILURE_VALUE;
-+
-+ try {
-+ data = ChunkSerializer.saveChunk(this.world, this.chunk, this.asyncSaveData);
-+ PaperFileIOThread.LOGGER.info("Successfully serialized chunk data for task: " + this.toString() + " synchronously");
-+ } catch (final Throwable ex1) {
-+ PaperFileIOThread.LOGGER.error("Failed to synchronously serialize unloading chunk data for task: " + this.toString() + "! Chunk data will be lost", ex1);
-+ }
-+
-+ ChunkSaveTask.this.complete(data);
-+ }
-+ });
-+
-+ return; // the main thread will now complete the data
-+ }
-+
-+ this.complete(compound);
-+ }
-+
-+ @Override
-+ public boolean raisePriority(final int priority) {
-+ if (!PrioritizedTaskQueue.validPriority(priority)) {
-+ throw new IllegalStateException("Invalid priority: " + priority);
-+ }
-+
-+ // we know priority is valid here
-+ for (int curr = this.attemptedPriority.get();;) {
-+ if (curr <= priority) {
-+ break; // curr is higher/same priority
-+ }
-+ if (this.attemptedPriority.compareAndSet(curr, priority)) {
-+ break;
-+ }
-+ curr = this.attemptedPriority.get();
-+ }
-+
-+ return super.raisePriority(priority);
-+ }
-+
-+ @Override
-+ public boolean updatePriority(final int priority) {
-+ if (!PrioritizedTaskQueue.validPriority(priority)) {
-+ throw new IllegalStateException("Invalid priority: " + priority);
-+ }
-+ this.attemptedPriority.set(priority);
-+ return super.updatePriority(priority);
-+ }
-+
-+ private void complete(final CompoundTag compound) {
-+ try {
-+ this.onComplete.complete(compound);
-+ } catch (final Throwable thr) {
-+ PaperFileIOThread.LOGGER.error("Failed to complete chunk data for task: " + this.toString(), thr);
-+ }
-+ if (compound != PaperFileIOThread.FAILURE_VALUE) {
-+ PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, this.chunkX, this.chunkZ, null, compound, this.attemptedPriority.get());
-+ }
-+ this.taskManager.chunkSaveTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(this.chunkX, this.chunkZ)), (final Long keyInMap, final ChunkSaveTask valueInMap) -> {
-+ if (valueInMap != ChunkSaveTask.this) {
-+ throw new IllegalStateException("Expected this task to be scheduled, but another was! Other: " + valueInMap + ", this: " + ChunkSaveTask.this);
-+ }
-+ return null;
-+ });
-+ }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTask.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTask.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..058fb5a41565e6ce2acbd1f4d071a1b8be449f5d
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTask.java
-@@ -0,0 +1,40 @@
-+package com.destroystokyo.paper.io.chunk;
-+
-+import com.destroystokyo.paper.io.PaperFileIOThread;
-+import com.destroystokyo.paper.io.PrioritizedTaskQueue;
-+import net.minecraft.server.level.ServerLevel;
-+
-+abstract class ChunkTask extends PrioritizedTaskQueue.PrioritizedTask implements Runnable {
-+
-+ public final ServerLevel world;
-+ public final int chunkX;
-+ public final int chunkZ;
-+ public final ChunkTaskManager taskManager;
-+
-+ public ChunkTask(final ServerLevel world, final int chunkX, final int chunkZ, final int priority,
-+ final ChunkTaskManager taskManager) {
-+ super(priority);
-+ this.world = world;
-+ this.chunkX = chunkX;
-+ this.chunkZ = chunkZ;
-+ this.taskManager = taskManager;
-+ }
-+
-+ @Override
-+ public String toString() {
-+ return "Chunk task: class:" + this.getClass().getName() + ", for world '" + this.world.getWorld().getName() +
-+ "', (" + this.chunkX + "," + this.chunkZ + "), hashcode:" + this.hashCode() + ", priority: " + this.getPriority();
-+ }
-+
-+ @Override
-+ public boolean raisePriority(final int priority) {
-+ PaperFileIOThread.Holder.INSTANCE.bumpPriority(this.world, this.chunkX, this.chunkZ, priority);
-+ return super.raisePriority(priority);
-+ }
-+
-+ @Override
-+ public boolean updatePriority(final int priority) {
-+ PaperFileIOThread.Holder.INSTANCE.setPriority(this.world, this.chunkX, this.chunkZ, priority);
-+ return super.updatePriority(priority);
-+ }
-+}
-diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..af40e473521f408aa0e112953c43bdbce164a48b
---- /dev/null
-+++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java
-@@ -0,0 +1,535 @@
-+package com.destroystokyo.paper.io.chunk;
-+
-+import com.destroystokyo.paper.io.PaperFileIOThread;
-+import com.destroystokyo.paper.io.IOUtil;
-+import com.destroystokyo.paper.io.PrioritizedTaskQueue;
-+import com.destroystokyo.paper.io.QueueExecutorThread;
-+import io.papermc.paper.configuration.GlobalConfiguration;
-+import net.minecraft.nbt.CompoundTag;
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ChunkHolder;
-+import net.minecraft.server.level.ServerChunkCache;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.util.thread.BlockableEventLoop;
-+import net.minecraft.world.level.chunk.ChunkAccess;
-+import net.minecraft.world.level.chunk.ChunkStatus;
-+import net.minecraft.world.level.chunk.storage.ChunkSerializer;
-+import org.apache.commons.lang.StringUtils;
-+import org.apache.logging.log4j.Level;
-+import org.bukkit.Bukkit;
-+import org.spigotmc.AsyncCatcher;
-+
-+import java.util.ArrayDeque;
-+import java.util.HashSet;
-+import java.util.Set;
-+import java.util.concurrent.CompletableFuture;
-+import java.util.concurrent.ConcurrentHashMap;
-+import java.util.concurrent.ConcurrentLinkedQueue;
-+import java.util.function.Consumer;
-+
-+public final class ChunkTaskManager {
-+
-+ private final QueueExecutorThread<ChunkTask>[] workers;
-+ private final ServerLevel world;
-+
-+ private final PrioritizedTaskQueue<ChunkTask> queue;
-+ private final boolean perWorldQueue;
-+
-+ final ConcurrentHashMap<Long, ChunkLoadTask> chunkLoadTasks = new ConcurrentHashMap<>(64, 0.5f);
-+ final ConcurrentHashMap<Long, ChunkSaveTask> chunkSaveTasks = new ConcurrentHashMap<>(64, 0.5f);
-+
-+ private final PrioritizedTaskQueue<ChunkTask> chunkTasks = new PrioritizedTaskQueue<>(); // used if async chunks are disabled in config
-+
-+ protected static QueueExecutorThread<ChunkTask>[] globalWorkers;
-+ protected static PrioritizedTaskQueue<ChunkTask> globalQueue;
-+
-+ protected static final ConcurrentLinkedQueue<Runnable> CHUNK_WAIT_QUEUE = new ConcurrentLinkedQueue<>();
-+
-+ public static final ArrayDeque<ChunkInfo> WAITING_CHUNKS = new ArrayDeque<>(); // stack
-+
-+ private static final class ChunkInfo {
-+
-+ public final int chunkX;
-+ public final int chunkZ;
-+ public final ServerLevel world;
-+
-+ public ChunkInfo(final int chunkX, final int chunkZ, final ServerLevel world) {
-+ this.chunkX = chunkX;
-+ this.chunkZ = chunkZ;
-+ this.world = world;
-+ }
-+
-+ @Override
-+ public String toString() {
-+ return "[( " + this.chunkX + "," + this.chunkZ + ") in '" + this.world.getWorld().getName() + "']";
-+ }
-+ }
-+
-+ public static void pushChunkWait(final ServerLevel world, final int chunkX, final int chunkZ) {
-+ synchronized (WAITING_CHUNKS) {
-+ WAITING_CHUNKS.push(new ChunkInfo(chunkX, chunkZ, world));
-+ }
-+ }
-+
-+ public static void popChunkWait() {
-+ synchronized (WAITING_CHUNKS) {
-+ WAITING_CHUNKS.pop();
-+ }
-+ }
-+
-+ private static ChunkInfo[] getChunkInfos() {
-+ ChunkInfo[] chunks;
-+ synchronized (WAITING_CHUNKS) {
-+ chunks = WAITING_CHUNKS.toArray(new ChunkInfo[0]);
-+ }
-+ return chunks;
-+ }
-+
-+ public static void dumpAllChunkLoadInfo() {
-+ ChunkInfo[] chunks = getChunkInfos();
-+ if (chunks.length > 0) {
-+ PaperFileIOThread.LOGGER.error("Chunk wait task info below: ");
-+
-+ for (final ChunkInfo chunkInfo : chunks) {
-+ final long key = IOUtil.getCoordinateKey(chunkInfo.chunkX, chunkInfo.chunkZ);
-+ final ChunkLoadTask loadTask = chunkInfo.world.asyncChunkTaskManager.chunkLoadTasks.get(key);
-+ final ChunkSaveTask saveTask = chunkInfo.world.asyncChunkTaskManager.chunkSaveTasks.get(key);
-+
-+ PaperFileIOThread.LOGGER.error(chunkInfo.chunkX + "," + chunkInfo.chunkZ + " in '" + chunkInfo.world.getWorld().getName() + ":");
-+ PaperFileIOThread.LOGGER.error("Load Task - " + (loadTask == null ? "none" : loadTask.toString()));
-+ PaperFileIOThread.LOGGER.error("Save Task - " + (saveTask == null ? "none" : saveTask.toString()));
-+ // log current status of chunk to indicate whether we're waiting on generation or loading
-+ ChunkHolder chunkHolder = chunkInfo.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(key);
-+
-+ dumpChunkInfo(new HashSet<>(), chunkHolder, chunkInfo.chunkX, chunkInfo.chunkZ);
-+ }
-+ }
-+ }
-+
-+ static void dumpChunkInfo(Set<ChunkHolder> seenChunks, ChunkHolder chunkHolder, int x, int z) {
-+ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1);
-+ }
-+
-+ static void dumpChunkInfo(Set<ChunkHolder> seenChunks, ChunkHolder chunkHolder, int x, int z, int indent, int maxDepth) {
-+ if (seenChunks.contains(chunkHolder)) {
-+ return;
-+ }
-+ if (indent > maxDepth) {
-+ return;
-+ }
-+ seenChunks.add(chunkHolder);
-+ String indentStr = StringUtils.repeat(" ", indent);
-+ if (chunkHolder == null) {
-+ PaperFileIOThread.LOGGER.error(indentStr + "Chunk Holder - null for (" + x +"," + z +")");
-+ } else {
-+ ChunkAccess chunk = chunkHolder.getLastAvailable();
-+ ChunkStatus holderStatus = chunkHolder.getChunkHolderStatus();
-+ PaperFileIOThread.LOGGER.error(indentStr + "Chunk Holder - non-null");
-+ PaperFileIOThread.LOGGER.error(indentStr + "Chunk Status - " + ((chunk == null) ? "null chunk" : chunk.getStatus().toString()));
-+ PaperFileIOThread.LOGGER.error(indentStr + "Chunk Ticket Status - " + ChunkHolder.getStatus(chunkHolder.getTicketLevel()));
-+ PaperFileIOThread.LOGGER.error(indentStr + "Chunk Holder Status - " + ((holderStatus == null) ? "null" : holderStatus.toString()));
-+ }
-+ }
-+
-+ public static void processConfiguration(GlobalConfiguration.AsyncChunks config) {
-+ int threads = config.threads; // don't write back to config
-+ int cpus = Runtime.getRuntime().availableProcessors() / 2;
-+ if (threads <= 0) {
-+ if (cpus <= 4) {
-+ threads = cpus <= 2 ? 1 : 2;
-+ } else {
-+ threads = (int) Math.min(Integer.getInteger("paper.maxChunkThreads", 4), cpus / 2);
-+ }
-+ }
-+ if (cpus == 1 && !Boolean.getBoolean("Paper.allowAsyncChunksSingleCore")) {
-+ config.asyncChunks = false;
-+ } else {
-+ config.asyncChunks = true;
-+ }
-+
-+ // Let Shared Host set some limits
-+ String sharedHostThreads = System.getenv("PAPER_ASYNC_CHUNKS_SHARED_HOST_THREADS");
-+ if (sharedHostThreads != null) {
-+ try {
-+ threads = Math.max(1, Math.min(threads, Integer.parseInt(sharedHostThreads)));
-+ } catch (NumberFormatException ignored) {}
-+ }
-+
-+ if (config.asyncChunks) {
-+ ChunkTaskManager.initGlobalLoadThreads(threads);
-+ }
-+ }
-+
-+ public static void initGlobalLoadThreads(int threads) {
-+ if (threads <= 0 || globalWorkers != null) {
-+ return;
-+ }
-+ ++threads; // add one for urgent executor
-+
-+ globalWorkers = new QueueExecutorThread[threads];
-+ globalQueue = new PrioritizedTaskQueue<>();
-+
-+ for (int i = 0; i < (threads - 1); ++i) {
-+ globalWorkers[i] = new QueueExecutorThread<>(globalQueue, (long)0.10e6); //0.1ms
-+ globalWorkers[i].setName("Paper Async Chunk Task Thread #" + i);
-+ globalWorkers[i].setPriority(Thread.NORM_PRIORITY - 1);
-+ globalWorkers[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> {
-+ PaperFileIOThread.LOGGER.error("Thread '" + thread.getName() + "' threw an uncaught exception!", throwable);
-+ });
-+
-+ globalWorkers[i].start();
-+ }
-+
-+ globalWorkers[threads - 1] = new QueueExecutorThread<>(globalQueue, (long)0.10e6); //0.1ms
-+ globalWorkers[threads - 1].setName("Paper Async Chunk Urgent Task Thread");
-+ globalWorkers[threads - 1].setPriority(Thread.NORM_PRIORITY+1);
-+ globalWorkers[threads - 1].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> {
-+ PaperFileIOThread.LOGGER.error("Thread '" + thread.getName() + "' threw an uncaught exception!", throwable);
-+ });
-+ globalWorkers[threads - 1].setLowestPriorityToPoll(PrioritizedTaskQueue.HIGHEST_PRIORITY);
-+ globalWorkers[threads - 1].start();
-+ }
-+
-+ /**
-+ * Creates this chunk task manager to operate off the specified number of threads. If the specified number of threads is
-+ * less-than or equal to 0, then this chunk task manager will operate off of the world's chunk task queue.
-+ * @param world Specified world.
-+ * @param threads Specified number of threads.
-+ * @see ServerChunkCache#mainThreadProcessor
-+ */
-+ public ChunkTaskManager(final ServerLevel world, final int threads) {
-+ this.world = world;
-+ this.workers = threads <= 0 ? null : new QueueExecutorThread[threads];
-+ this.queue = new PrioritizedTaskQueue<>();
-+ this.perWorldQueue = true;
-+
-+ for (int i = 0; i < threads; ++i) {
-+ this.workers[i] = new QueueExecutorThread<>(this.queue, (long)0.10e6); //0.1ms
-+ this.workers[i].setName("Async chunk loader thread #" + i + " for world: " + world.getWorld().getName());
-+ this.workers[i].setPriority(Thread.NORM_PRIORITY - 1);
-+ this.workers[i].setUncaughtExceptionHandler((final Thread thread, final Throwable throwable) -> {
-+ PaperFileIOThread.LOGGER.error("Thread '" + thread.getName() + "' threw an uncaught exception!", throwable);
-+ });
-+
-+ this.workers[i].start();
-+ }
-+ }
-+
-+ /**
-+ * Creates the chunk task manager to work from the global workers. When {@link #close(boolean)} is invoked,
-+ * the global queue is not shutdown. If the global workers is configured to be disabled or use 0 threads, then
-+ * this chunk task manager will operate off of the world's chunk task queue.
-+ * @param world The world that this task manager is responsible for
-+ * @see ServerChunkCache#mainThreadProcessor
-+ */
-+ public ChunkTaskManager(final ServerLevel world) {
-+ this.world = world;
-+ this.workers = globalWorkers;
-+ this.queue = globalQueue;
-+ this.perWorldQueue = false;
-+ }
-+
-+ public boolean pollNextChunkTask() {
-+ final ChunkTask task = this.chunkTasks.poll();
-+
-+ if (task != null) {
-+ task.run();
-+ return true;
-+ }
-+ return false;
-+ }
-+
-+ /**
-+ * Polls and runs the next available chunk wait queue task. This is to be used when the server is waiting on a chunk queue.
-+ * (per-world can cause issues if all the worker threads are blocked waiting for a response from the main thread)
-+ */
-+ public static boolean pollChunkWaitQueue() {
-+ final Runnable run = CHUNK_WAIT_QUEUE.poll();
-+ if (run != null) {
-+ run.run();
-+ return true;
-+ }
-+ return false;
-+ }
-+
-+ /**
-+ * Queues a chunk wait task. Note that this will execute out of order with respect to tasks scheduled on a world's
-+ * chunk task queue, since this is the global chunk wait queue.
-+ */
-+ public static void queueChunkWaitTask(final Runnable runnable) {
-+ CHUNK_WAIT_QUEUE.add(runnable);
-+ }
-+
-+ private static void drainChunkWaitQueue() {
-+ Runnable run;
-+ while ((run = CHUNK_WAIT_QUEUE.poll()) != null) {
-+ run.run();
-+ }
-+ }
-+
-+ /**
-+ * The exact same as {@link #scheduleChunkLoad(int, int, int, Consumer, boolean)}, except that the chunk data is provided as
-+ * the {@code data} parameter.
-+ */
-+ public ChunkLoadTask scheduleChunkLoad(final int chunkX, final int chunkZ, final int priority,
-+ final Consumer<ChunkSerializer.InProgressChunkHolder> onComplete,
-+ final boolean intendingToBlock, final CompletableFuture<CompoundTag> dataFuture) {
-+ final ServerLevel world = this.world;
-+
-+ return this.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> {
-+ if (valueInMap != null) {
-+ if (!valueInMap.cancelled) {
-+ throw new IllegalStateException("Double scheduling chunk load for task: " + valueInMap.toString());
-+ }
-+ valueInMap.cancelled = false;
-+ valueInMap.onComplete = onComplete;
-+ return valueInMap;
-+ }
-+
-+ final ChunkLoadTask ret = new ChunkLoadTask(world, chunkX, chunkZ, priority, ChunkTaskManager.this, onComplete);
-+
-+ dataFuture.thenAccept((final CompoundTag data) -> {
-+ final boolean failed = data == PaperFileIOThread.FAILURE_VALUE;
-+ PaperFileIOThread.Holder.INSTANCE.loadChunkDataAsync(world, chunkX, chunkZ, priority, (final PaperFileIOThread.ChunkData chunkData) -> {
-+ ret.chunkData = chunkData;
-+ if (!failed) {
-+ chunkData.chunkData = data;
-+ }
-+ ChunkTaskManager.this.internalSchedule(ret); // only schedule to the worker threads here
-+ }, true, failed, intendingToBlock); // read data off disk if the future fails
-+ });
-+
-+ return ret;
-+ });
-+ }
-+
-+ public void cancelChunkLoad(final int chunkX, final int chunkZ) {
-+ this.chunkLoadTasks.compute(IOUtil.getCoordinateKey(chunkX, chunkZ), (final Long keyInMap, final ChunkLoadTask valueInMap) -> {
-+ if (valueInMap == null) {
-+ return null;
-+ }
-+
-+ if (valueInMap.cancelled) {
-+ PaperFileIOThread.LOGGER.warn("Task " + valueInMap.toString() + " is already cancelled!");
-+ }
-+ valueInMap.cancelled = true;
-+ if (valueInMap.cancel()) {
-+ return null;
-+ }
-+
-+ return valueInMap;
-+ });
-+ }
-+
-+ /**
-+ * Schedules an asynchronous chunk load for the specified coordinates. The onComplete parameter may be invoked asynchronously
-+ * on a worker thread or on the world's chunk executor queue. As such the code that is executed for the parameter should be
-+ * carefully chosen.
-+ * @param chunkX Chunk's x coordinate
-+ * @param chunkZ Chunk's z coordinate
-+ * @param priority Priority for this task
-+ * @param onComplete The consumer to invoke with the {@link ChunkSerializer.InProgressChunkHolder} object once this task is complete
-+ * @param intendingToBlock Whether the caller is intending to block on this task completing (this is a performance tune, and has no adverse side-effects)
-+ * @return The {@link ChunkLoadTask} associated with
-+ */
-+ public ChunkLoadTask scheduleChunkLoad(final int chunkX, final int chunkZ, final int priority,
-+ final Consumer<ChunkSerializer.InProgressChunkHolder> onComplete,
-+ final boolean intendingToBlock) {
-+ final ServerLevel world = this.world;
-+
-+ return this.chunkLoadTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkLoadTask valueInMap) -> {
-+ if (valueInMap != null) {
-+ if (!valueInMap.cancelled) {
-+ throw new IllegalStateException("Double scheduling chunk load for task: " + valueInMap.toString());
-+ }
-+ valueInMap.cancelled = false;
-+ valueInMap.onComplete = onComplete;
-+ return valueInMap;
-+ }
-+
-+ final ChunkLoadTask ret = new ChunkLoadTask(world, chunkX, chunkZ, priority, ChunkTaskManager.this, onComplete);
-+
-+ PaperFileIOThread.Holder.INSTANCE.loadChunkDataAsync(world, chunkX, chunkZ, priority, (final PaperFileIOThread.ChunkData chunkData) -> {
-+ ret.chunkData = chunkData;
-+ ChunkTaskManager.this.internalSchedule(ret); // only schedule to the worker threads here
-+ }, true, true, intendingToBlock);
-+
-+ return ret;
-+ });
-+ }
-+
-+ /**
-+ * Schedules an async save for the specified chunk. The chunk, at the beginning of this call, must be completely unloaded
-+ * from the world.
-+ * @param chunkX Chunk's x coordinate
-+ * @param chunkZ Chunk's z coordinate
-+ * @param priority Priority for this task
-+ * @param asyncSaveData Async save data. See {@link ChunkSerializer#getAsyncSaveData(ServerLevel, ChunkAccess)}
-+ * @param chunk Chunk to save
-+ * @return The {@link ChunkSaveTask} associated with the save task.
-+ */
-+ public ChunkSaveTask scheduleChunkSave(final int chunkX, final int chunkZ, final int priority,
-+ final ChunkSerializer.AsyncSaveData asyncSaveData,
-+ final ChunkAccess chunk) {
-+ AsyncCatcher.catchOp("chunk save schedule");
-+
-+ final ServerLevel world = this.world;
-+
-+ return this.chunkSaveTasks.compute(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)), (final Long keyInMap, final ChunkSaveTask valueInMap) -> {
-+ if (valueInMap != null) {
-+ throw new IllegalStateException("Double scheduling chunk save for task: " + valueInMap.toString());
-+ }
-+
-+ final ChunkSaveTask ret = new ChunkSaveTask(world, chunkX, chunkZ, priority, ChunkTaskManager.this, asyncSaveData, chunk);
-+
-+ ChunkTaskManager.this.internalSchedule(ret);
-+
-+ return ret;
-+ });
-+ }
-+
-+ /**
-+ * Returns a completable future which will be completed with the <b>un-copied</b> chunk data for an in progress async save.
-+ * Returns {@code null} if no save is in progress.
-+ * @param chunkX Chunk's x coordinate
-+ * @param chunkZ Chunk's z coordinate
-+ */
-+ public CompletableFuture<CompoundTag> getChunkSaveFuture(final int chunkX, final int chunkZ) {
-+ final ChunkSaveTask chunkSaveTask = this.chunkSaveTasks.get(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)));
-+ if (chunkSaveTask == null) {
-+ return null;
-+ }
-+ return chunkSaveTask.onComplete;
-+ }
-+
-+ /**
-+ * Returns the chunk object being used to serialize data async for an unloaded chunk. Note that modifying this chunk
-+ * is not safe to do as another thread is handling its save. The chunk is also not loaded into the world.
-+ * @param chunkX Chunk's x coordinate
-+ * @param chunkZ Chunk's z coordinate
-+ * @return Chunk object for an in-progress async save, or {@code null} if no save is in progress
-+ */
-+ public ChunkAccess getChunkInSaveProgress(final int chunkX, final int chunkZ) {
-+ final ChunkSaveTask chunkSaveTask = this.chunkSaveTasks.get(Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ)));
-+ if (chunkSaveTask == null) {
-+ return null;
-+ }
-+ return chunkSaveTask.chunk;
-+ }
-+
-+ public void flush() {
-+ // flush here since we schedule tasks on the IO thread that can schedule tasks here
-+ drainChunkWaitQueue();
-+ PaperFileIOThread.Holder.INSTANCE.flush();
-+ drainChunkWaitQueue();
-+
-+ if (this.workers == null) {
-+ if (Bukkit.isPrimaryThread() || MinecraftServer.getServer().hasStopped()) {
-+ ((BlockableEventLoop<Runnable>)this.world.getChunkSource().mainThreadProcessor).runAllTasks();
-+ } else {
-+ CompletableFuture<Void> wait = new CompletableFuture<>();
-+ MinecraftServer.getServer().scheduleOnMain(() -> {
-+ ((BlockableEventLoop<Runnable>)this.world.getChunkSource().mainThreadProcessor).runAllTasks();
-+ });
-+ wait.join();
-+ }
-+ } else {
-+ for (final QueueExecutorThread<ChunkTask> worker : this.workers) {
-+ worker.flush();
-+ }
-+ }
-+
-+ // flush again since tasks we execute async saves
-+ drainChunkWaitQueue();
-+ PaperFileIOThread.Holder.INSTANCE.flush();
-+ }
-+
-+ public void close(final boolean wait) {
-+ // flush here since we schedule tasks on the IO thread that can schedule tasks to this task manager
-+ // we do this regardless of the wait param since after we invoke close no tasks can be queued
-+ PaperFileIOThread.Holder.INSTANCE.flush();
-+
-+ if (this.workers == null) {
-+ if (wait) {
-+ this.flush();
-+ }
-+ return;
-+ }
-+
-+ if (this.workers != globalWorkers) {
-+ for (final QueueExecutorThread<ChunkTask> worker : this.workers) {
-+ worker.close(false, this.perWorldQueue);
-+ }
-+ }
-+
-+ if (wait) {
-+ this.flush();
-+ }
-+ }
-+
-+ public void raisePriority(final int chunkX, final int chunkZ, final int priority) {
-+ final Long chunkKey = Long.valueOf(IOUtil.getCoordinateKey(chunkX, chunkZ));
-+
-+ ChunkTask chunkSaveTask = this.chunkSaveTasks.get(chunkKey);
-+ if (chunkSaveTask != null) {
-+ // don't bump save into urgent queue
-+ raiseTaskPriority(chunkSaveTask, priority != PrioritizedTaskQueue.HIGHEST_PRIORITY ? priority : PrioritizedTaskQueue.HIGH_PRIORITY);
-+ }
-+
-+ ChunkLoadTask chunkLoadTask = this.chunkLoadTasks.get(chunkKey);
-+ if (chunkLoadTask != null) {
-+ raiseTaskPriority(chunkLoadTask, priority);
-+ }
-+ }
-+
-+ private void raiseTaskPriority(ChunkTask task, int priority) {
-+ final boolean raised = task.raisePriority(priority);
-+ if (task.isScheduled() && raised && this.workers != null) {
-+ // only notify if we're in queue to be executed
-+ if (priority == PrioritizedTaskQueue.HIGHEST_PRIORITY) {
-+ // notify urgent worker as well
-+ this.internalScheduleNotifyUrgent();
-+ }
-+ this.internalScheduleNotify();
-+ }
-+ }
-+
-+ protected void internalSchedule(final ChunkTask task) {
-+ if (this.workers == null) {
-+ this.chunkTasks.add(task);
-+ return;
-+ }
-+
-+ // It's important we order the task to be executed before notifying. Avoid a race condition where the worker thread
-+ // wakes up and goes to sleep before we actually schedule (or it's just about to sleep)
-+ this.queue.add(task);
-+ this.internalScheduleNotify();
-+ if (task.getPriority() == PrioritizedTaskQueue.HIGHEST_PRIORITY) {
-+ // notify urgent too
-+ this.internalScheduleNotifyUrgent();
-+ }
-+
-+ }
-+
-+ protected void internalScheduleNotify() {
-+ if (this.workers == null) {
-+ return;
-+ }
-+ for (int i = 0, len = this.workers.length - 1; i < len; ++i) {
-+ final QueueExecutorThread<ChunkTask> worker = this.workers[i];
-+ if (worker.notifyTasks()) {
-+ // break here since we only want to wake up one worker for scheduling one task
-+ break;
-+ }
-+ }
-+ }
-+
-+
-+ protected void internalScheduleNotifyUrgent() {
-+ if (this.workers == null) {
-+ return;
-+ }
-+ this.workers[this.workers.length - 1].notifyTasks();
-+ }
-+
-+}
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java
-index a5e438a834826161c52ca9db57d234d9ff80a591..b8bc1b9b8e8a33df90a963f9f9769292bf595642 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ServerboundCommandSuggestionPacket.java
-@@ -14,7 +14,7 @@ public class ServerboundCommandSuggestionPacket implements Packet<ServerGamePack
-
- public ServerboundCommandSuggestionPacket(FriendlyByteBuf buf) {
- this.id = buf.readVarInt();
-- this.command = buf.readUtf(32500);
-+ this.command = buf.readUtf(2048);
- }
-
- @Override
-diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
-index 4dd3af1416cbdad330365a19ad664079f3598c15..45db9f1b1d19319e7f92bd4e61be9ea9b06dd5e5 100644
---- a/src/main/java/net/minecraft/server/Main.java
-+++ b/src/main/java/net/minecraft/server/Main.java
-@@ -262,6 +262,7 @@ public class Main {
-
- convertable_conversionsession.saveDataTag(iregistrycustom_dimension, savedata);
- */
-+ Class.forName(net.minecraft.world.entity.npc.VillagerTrades.class.getName()); // Paper - load this sync so it won't fail later async
- final DedicatedServer dedicatedserver = (DedicatedServer) MinecraftServer.spin((thread) -> {
- DedicatedServer dedicatedserver1 = new DedicatedServer(optionset, config.get(), ops.get(), thread, convertable_conversionsession, resourcepackrepository, worldstem, dedicatedserversettings, DataFixers.getDataFixer(), services, LoggerChunkProgressListener::new);
-
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 53be6189d3fa6a65a09996683913fbbf5133dcb7..d53fb6bba90936c1182b0687d014964cef81694f 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -932,7 +932,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- this.getProfileCache().save();
- }
- // Spigot end
--
-+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.close(true, true); // Paper
- }
-
- public String getLocalIp() {
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 4da0cbe58dad0f66e0d056c71684120514dcac6a..c3bbaf32373a32417f8b83f386f8cf327c6e0893 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -545,6 +545,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- public void close() throws IOException {
- try {
- this.queueSorter.close();
-+ this.level.asyncChunkTaskManager.close(true); // Paper - Required since we're closing regionfiles in the next line
- this.poiManager.close();
- } finally {
- super.close();
-@@ -553,6 +554,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
-
- protected void saveAllChunks(boolean flush) {
-+ // Paper start - do not overload I/O threads with too much work when saving
-+ int[] saved = new int[1];
-+ int maxAsyncSaves = 50;
-+ Runnable onChunkSave = () -> {
-+ if (++saved[0] >= maxAsyncSaves) {
-+ saved[0] = 0;
-+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.flush();
-+ }
-+ };
-+ // Paper end - do not overload I/O threads with too much work when saving
- if (flush) {
- List<ChunkHolder> list = (List) net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList()); // Paper
- MutableBoolean mutableboolean = new MutableBoolean();
-@@ -574,6 +585,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }).filter((ichunkaccess) -> {
- return ichunkaccess instanceof ImposterProtoChunk || ichunkaccess instanceof LevelChunk;
- }).filter(this::save).forEach((ichunkaccess) -> {
-+ onChunkSave.run(); // Paper - do not overload I/O threads with too much work when saving
- mutableboolean.setTrue();
- });
- } while (mutableboolean.isTrue());
-@@ -581,7 +593,8 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- this.processUnloads(() -> {
- return true;
- });
-- this.flushWorker();
-+ //this.flushWorker(); // Paper - nuke IOWorker
-+ this.level.asyncChunkTaskManager.flush(); // Paper - flush to preserve behavior compat with pre-async behaviour
- } else {
- net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).forEach(this::saveChunkIfNeeded);
- }
-@@ -591,11 +604,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- protected void tick(BooleanSupplier shouldKeepTicking) {
- ProfilerFiller gameprofilerfiller = this.level.getProfiler();
-
-+ try (Timing ignored = this.level.timings.poiUnload.startTiming()) { // Paper
- gameprofilerfiller.push("poi");
- this.poiManager.tick(shouldKeepTicking);
-+ } // Paper
- gameprofilerfiller.popPush("chunk_unload");
- if (!this.level.noSave()) {
-+ try (Timing ignored = this.level.timings.chunkUnload.startTiming()) { // Paper
- this.processUnloads(shouldKeepTicking);
-+ } // Paper
- }
-
- gameprofilerfiller.pop();
-@@ -658,7 +675,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- ((LevelChunk) ichunkaccess).setLoaded(false);
- }
-
-- this.save(ichunkaccess);
-+ // Paper start - async chunk saving
-+ try {
-+ this.asyncSave(ichunkaccess);
-+ } catch (ThreadDeath ex) {
-+ throw ex; // bye
-+ } catch (Throwable ex) {
-+ LOGGER.error("Failed to prepare async save, attempting synchronous save", ex);
-+ this.save(ichunkaccess);
-+ }
-+ // Paper end - async chunk saving
- if (this.entitiesInLevel.remove(pos) && ichunkaccess instanceof LevelChunk) {
- LevelChunk chunk = (LevelChunk) ichunkaccess;
-
-@@ -727,32 +753,54 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
-
- private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> scheduleChunkLoad(ChunkPos pos) {
-- return this.readChunk(pos).thenApply((optional) -> {
-- return optional.filter((nbttagcompound) -> {
-- boolean flag = ChunkMap.isChunkDataValid(nbttagcompound);
-+ // Paper start - Async chunk io
-+ final java.util.function.BiFunction<ChunkSerializer.InProgressChunkHolder, Throwable, Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> syncLoadComplete = (chunkHolder, ioThrowable) -> {
-+ try (Timing ignored = this.level.timings.chunkLoad.startTimingIfSync()) { // Paper
-+ this.level.getProfiler().incrementCounter("chunkLoad");
-+ if (ioThrowable != null) {
-+ return this.handleChunkLoadFailure(ioThrowable, pos);
-+ }
-+ this.poiManager.loadInData(pos, chunkHolder.poiData);
-+ chunkHolder.tasks.forEach(Runnable::run);
-
-- if (!flag) {
-- ChunkMap.LOGGER.error("Chunk file at {} is missing level data, skipping", pos);
-+ if (chunkHolder.protoChunk != null) {
-+ ProtoChunk protochunk = chunkHolder.protoChunk;
-+ this.markPosition(pos, protochunk.getStatus().getChunkType());
-+ return Either.left(protochunk);
- }
-+ } catch (Exception ex) {
-+ return this.handleChunkLoadFailure(ex, pos);
-+ }
-
-- return flag;
-+ return Either.left(this.createEmptyChunk(pos));
-+ };
-+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> ret = new CompletableFuture<>();
-+
-+ Consumer<ChunkSerializer.InProgressChunkHolder> chunkHolderConsumer = (ChunkSerializer.InProgressChunkHolder holder) -> {
-+ // Go into the chunk load queue and not server task queue so we can be popped out even faster.
-+ com.destroystokyo.paper.io.chunk.ChunkTaskManager.queueChunkWaitTask(() -> {
-+ try {
-+ ret.complete(syncLoadComplete.apply(holder, null));
-+ } catch (Exception e) {
-+ ret.completeExceptionally(e);
-+ }
- });
-- }).thenApplyAsync((optional) -> {
-- this.level.getProfiler().incrementCounter("chunkLoad");
-- if (optional.isPresent()) {
-- ProtoChunk protochunk = ChunkSerializer.read(this.level, this.poiManager, pos, (CompoundTag) optional.get());
-+ };
-
-- this.markPosition(pos, protochunk.getStatus().getChunkType());
-- return Either.<ChunkAccess, ChunkHolder.ChunkLoadingFailure>left(protochunk); // CraftBukkit - decompile error
-- } else {
-- return Either.<ChunkAccess, ChunkHolder.ChunkLoadingFailure>left(this.createEmptyChunk(pos)); // CraftBukkit - decompile error
-- }
-- }, this.mainThreadExecutor).exceptionallyAsync((throwable) -> {
-- return this.handleChunkLoadFailure(throwable, pos);
-- }, this.mainThreadExecutor);
-+ CompletableFuture<CompoundTag> chunkSaveFuture = this.level.asyncChunkTaskManager.getChunkSaveFuture(pos.x, pos.z);
-+ if (chunkSaveFuture != null) {
-+ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z,
-+ com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture);
-+ this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY);
-+ } else {
-+ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z,
-+ com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false);
-+ }
-+ return ret;
-+ // Paper end - Async chunk io
- }
-
-- private static boolean isChunkDataValid(CompoundTag nbt) {
-+ public static boolean isChunkDataValid(CompoundTag nbt) { // Paper - async chunk loading
- return nbt.contains("Status", 8);
- }
-
-@@ -991,7 +1039,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
- }
-
-+ // Paper start - async chunk save for unload
-+ // Note: This is very unsafe to call if the chunk is still in use.
-+ // This is also modeled after PlayerChunkMap#save(IChunkAccess, boolean), with the intentional difference being
-+ // serializing the chunk is left to a worker thread.
-+ private void asyncSave(ChunkAccess chunk) {
-+ ChunkPos chunkPos = chunk.getPos();
-+ CompoundTag poiData;
-+ try (Timing ignored = this.level.timings.chunkUnloadPOISerialization.startTiming()) {
-+ poiData = this.poiManager.getData(chunk.getPos());
-+ }
-+
-+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.level, chunkPos.x, chunkPos.z,
-+ poiData, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY);
-+
-+ if (!chunk.isUnsaved()) {
-+ return;
-+ }
-+
-+ ChunkStatus chunkstatus = chunk.getStatus();
-+
-+ // Copied from PlayerChunkMap#save(IChunkAccess, boolean)
-+ if (chunkstatus.getChunkType() != ChunkStatus.ChunkType.LEVELCHUNK) {
-+ // Paper start - Optimize save by using status cache
-+ if (chunkstatus == ChunkStatus.EMPTY && chunk.getAllStarts().values().stream().noneMatch(StructureStart::isValid)) {
-+ return;
-+ }
-+ }
-+
-+ ChunkSerializer.AsyncSaveData asyncSaveData;
-+ try (Timing ignored = this.level.timings.chunkUnloadPrepareSave.startTiming()) {
-+ asyncSaveData = ChunkSerializer.getAsyncSaveData(this.level, chunk);
-+ }
-+
-+ this.level.asyncChunkTaskManager.scheduleChunkSave(chunkPos.x, chunkPos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY,
-+ asyncSaveData, chunk);
-+
-+ chunk.setUnsaved(false);
-+ }
-+ // Paper end
-+
- public boolean save(ChunkAccess chunk) {
-+ try (co.aikar.timings.Timing ignored = this.level.timings.chunkSave.startTiming()) { // Paper
- this.poiManager.flush(chunk.getPos());
- if (!chunk.isUnsaved()) {
- return false;
-@@ -1003,7 +1092,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- ChunkStatus chunkstatus = chunk.getStatus();
-
- if (chunkstatus.getChunkType() != ChunkStatus.ChunkType.LEVELCHUNK) {
-- if (this.isExistingChunkFull(chunkcoordintpair)) {
-+ if (false && this.isExistingChunkFull(chunkcoordintpair)) { // Paper
- return false;
- }
-
-@@ -1013,9 +1102,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
-
- this.level.getProfiler().incrementCounter("chunkSave");
-- CompoundTag nbttagcompound = ChunkSerializer.write(this.level, chunk);
-+ CompoundTag nbttagcompound;
-+ try (co.aikar.timings.Timing ignored1 = this.level.timings.chunkSaveDataSerialization.startTiming()) { // Paper
-+ nbttagcompound = ChunkSerializer.write(this.level, chunk);
-+ } // Paper
-
-- this.write(chunkcoordintpair, nbttagcompound);
-+ // Paper start - async chunk io
-+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.level, chunkcoordintpair.x, chunkcoordintpair.z,
-+ null, nbttagcompound, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY);
-+ // Paper end - async chunk io
- this.markPosition(chunkcoordintpair, chunkstatus.getChunkType());
- return true;
- } catch (Exception exception) {
-@@ -1023,6 +1118,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- return false;
- }
- }
-+ } // Paper
- }
-
- private boolean isExistingChunkFull(ChunkPos pos) {
-@@ -1156,6 +1252,35 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
- }
-
-+ // Paper start - Asynchronous chunk io
-+ @Nullable
-+ @Override
-+ public CompoundTag readSync(ChunkPos chunkcoordintpair) throws IOException {
-+ if (Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) {
-+ CompoundTag ret = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE
-+ .loadChunkDataAsyncFuture(this.level, chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(),
-+ false, true, true).join().chunkData;
-+
-+ if (ret == com.destroystokyo.paper.io.PaperFileIOThread.FAILURE_VALUE) {
-+ throw new IOException("See logs for further detail");
-+ }
-+ return ret;
-+ }
-+ return super.readSync(chunkcoordintpair);
-+ }
-+
-+ @Override
-+ public void write(ChunkPos chunkcoordintpair, CompoundTag nbttagcompound) throws IOException {
-+ if (Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) {
-+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(
-+ this.level, chunkcoordintpair.x, chunkcoordintpair.z, null, nbttagcompound,
-+ com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread());
-+ return;
-+ }
-+ super.write(chunkcoordintpair, nbttagcompound);
-+ }
-+ // Paper end
-+
- private CompletableFuture<Optional<CompoundTag>> readChunk(ChunkPos chunkPos) {
- return this.read(chunkPos).thenApplyAsync((optional) -> {
- return optional.map((nbttagcompound) -> this.upgradeChunkTag(nbttagcompound, chunkPos)); // CraftBukkit
-diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index aaf6344d3187ceada947ce6ee0fbba91ca0271a3..1d6ab658c48bb765f66624f276ec7b05cf33c1d5 100644
---- a/src/main/java/net/minecraft/server/level/DistanceManager.java
-+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -402,7 +402,7 @@ public abstract class DistanceManager {
- }
-
- public void removeTicketsOnClosing() {
-- ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT); // Paper - add additional tickets to preserve
-+ ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD); // Paper - add additional tickets to preserve
- ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
-
- while (objectiterator.hasNext()) {
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 8c99e9d1cc1abf5a425846eb4edd52bf38aa2f75..07671ac54f598872dba2b22ec8f82db3dd037d7f 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -388,10 +388,33 @@ public class ServerChunkCache extends ChunkSource {
- return ret;
- }
- // Paper end
-+ // Paper start - async chunk io
-+ public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkAtAsynchronously(int x, int z, boolean gen, boolean isUrgent) {
-+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> ret = new CompletableFuture<>();
-+
-+ ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority priority;
-+ if (isUrgent) {
-+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.HIGHER;
-+ } else {
-+ priority = ca.spottedleaf.concurrentutil.executor.standard.PrioritisedExecutor.Priority.NORMAL;
-+ }
-+
-+ net.minecraft.server.ChunkSystem.scheduleChunkLoad(this.level, x, z, gen, ChunkStatus.FULL, true, priority, (chunk) -> {
-+ if (chunk == null) {
-+ ret.complete(ChunkHolder.UNLOADED_CHUNK);
-+ } else {
-+ ret.complete(Either.left(chunk));
-+ }
-+ });
-+
-+ return ret;
-+ }
-+ // Paper end - async chunk io
-
- @Nullable
- @Override
- public ChunkAccess getChunk(int x, int z, ChunkStatus leastStatus, boolean create) {
-+ final int x1 = x; final int z1 = z; // Paper - conflict on variable change
- if (Thread.currentThread() != this.mainThread) {
- return (ChunkAccess) CompletableFuture.supplyAsync(() -> {
- return this.getChunk(x, z, leastStatus, create);
-@@ -414,13 +437,18 @@ public class ServerChunkCache extends ChunkSource {
- }
-
- gameprofilerfiller.incrementCounter("getChunkCacheMiss");
-- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create);
-+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = this.getChunkFutureMainThread(x, z, leastStatus, create, true); // Paper
- ServerChunkCache.MainThreadExecutor chunkproviderserver_b = this.mainThreadProcessor;
-
- Objects.requireNonNull(completablefuture);
- if (!completablefuture.isDone()) { // Paper
-+ // Paper start - async chunk io/loading
-+ this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
-+ com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1);
-+ // Paper end
- this.level.timings.syncChunkLoad.startTiming(); // Paper
- chunkproviderserver_b.managedBlock(completablefuture::isDone);
-+ com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
- this.level.timings.syncChunkLoad.stopTiming(); // Paper
- } // Paper
- ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> {
-@@ -507,6 +535,11 @@ public class ServerChunkCache extends ChunkSource {
- }
-
- private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
-+ // Paper start - add isUrgent - old sig left in place for dirty nms plugins
-+ return getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create, false);
-+ }
-+ private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create, boolean isUrgent) {
-+ // Paper end
- ChunkPos chunkcoordintpair = new ChunkPos(chunkX, chunkZ);
- long k = chunkcoordintpair.toLong();
- int l = 33 + ChunkStatus.getDistance(leastStatus);
-@@ -921,11 +954,12 @@ public class ServerChunkCache extends ChunkSource {
- // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task
- public boolean pollTask() {
- try {
-+ boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask(); // Paper
- if (ServerChunkCache.this.runDistanceManagerUpdates()) {
- return true;
- } else {
- ServerChunkCache.this.lightEngine.tryScheduleUpdate();
-- return super.pollTask();
-+ return super.pollTask() || execChunkTask; // Paper
- }
- } finally {
- chunkMap.callbackExecutor.run();
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 99d44faab5b5da244fdc170c73d73723c174c8fd..2f7646e2bcc9622d8579eec25b56615da5a84d06 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -315,6 +315,78 @@ public class ServerLevel extends Level implements WorldGenLevel {
- }
- }
- }
-+
-+ // Paper start - Asynchronous IO
-+ public final com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController poiDataController = new com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController() {
-+ @Override
-+ public void writeData(int x, int z, net.minecraft.nbt.CompoundTag compound) throws java.io.IOException {
-+ ServerLevel.this.getChunkSource().chunkMap.getPoiManager().write(new ChunkPos(x, z), compound);
-+ }
-+
-+ @Override
-+ public net.minecraft.nbt.CompoundTag readData(int x, int z) throws java.io.IOException {
-+ return ServerLevel.this.getChunkSource().chunkMap.getPoiManager().read(new ChunkPos(x, z));
-+ }
-+
-+ @Override
-+ public <T> T computeForRegionFile(int chunkX, int chunkZ, java.util.function.Function<net.minecraft.world.level.chunk.storage.RegionFile, T> function) {
-+ synchronized (ServerLevel.this.getChunkSource().chunkMap.getPoiManager()) {
-+ net.minecraft.world.level.chunk.storage.RegionFile file;
-+
-+ try {
-+ file = ServerLevel.this.getChunkSource().chunkMap.getPoiManager().getRegionFile(new ChunkPos(chunkX, chunkZ), false);
-+ } catch (java.io.IOException ex) {
-+ throw new RuntimeException(ex);
-+ }
-+
-+ return function.apply(file);
-+ }
-+ }
-+
-+ @Override
-+ public <T> T computeForRegionFileIfLoaded(int chunkX, int chunkZ, java.util.function.Function<net.minecraft.world.level.chunk.storage.RegionFile, T> function) {
-+ synchronized (ServerLevel.this.getChunkSource().chunkMap.getPoiManager()) {
-+ net.minecraft.world.level.chunk.storage.RegionFile file = ServerLevel.this.getChunkSource().chunkMap.getPoiManager().getRegionFileIfLoaded(new ChunkPos(chunkX, chunkZ));
-+ return function.apply(file);
-+ }
-+ }
-+ };
-+
-+ public final com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController chunkDataController = new com.destroystokyo.paper.io.PaperFileIOThread.ChunkDataController() {
-+ @Override
-+ public void writeData(int x, int z, net.minecraft.nbt.CompoundTag compound) throws java.io.IOException {
-+ ServerLevel.this.getChunkSource().chunkMap.write(new ChunkPos(x, z), compound);
-+ }
-+
-+ @Override
-+ public net.minecraft.nbt.CompoundTag readData(int x, int z) throws java.io.IOException {
-+ return ServerLevel.this.getChunkSource().chunkMap.readSync(new ChunkPos(x, z));
-+ }
-+
-+ @Override
-+ public <T> T computeForRegionFile(int chunkX, int chunkZ, java.util.function.Function<net.minecraft.world.level.chunk.storage.RegionFile, T> function) {
-+ synchronized (ServerLevel.this.getChunkSource().chunkMap) {
-+ net.minecraft.world.level.chunk.storage.RegionFile file;
-+
-+ try {
-+ file = ServerLevel.this.getChunkSource().chunkMap.regionFileCache.getRegionFile(new ChunkPos(chunkX, chunkZ), false);
-+ } catch (java.io.IOException ex) {
-+ throw new RuntimeException(ex);
-+ }
-+
-+ return function.apply(file);
-+ }
-+ }
-+
-+ @Override
-+ public <T> T computeForRegionFileIfLoaded(int chunkX, int chunkZ, java.util.function.Function<net.minecraft.world.level.chunk.storage.RegionFile, T> function) {
-+ synchronized (ServerLevel.this.getChunkSource().chunkMap) {
-+ net.minecraft.world.level.chunk.storage.RegionFile file = ServerLevel.this.getChunkSource().chunkMap.regionFileCache.getRegionFileIfLoaded(new ChunkPos(chunkX, chunkZ));
-+ return function.apply(file);
-+ }
-+ }
-+ };
-+ public final com.destroystokyo.paper.io.chunk.ChunkTaskManager asyncChunkTaskManager;
- // Paper end
-
- // Add env and gen to constructor, IWorldDataServer -> WorldDataServer
-@@ -397,6 +469,8 @@ public class ServerLevel extends Level implements WorldGenLevel {
-
- this.sleepStatus = new SleepStatus();
- this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit
-+
-+ this.asyncChunkTaskManager = new com.destroystokyo.paper.io.chunk.ChunkTaskManager(this); // Paper
- }
-
- public void setWeatherParameters(int clearDuration, int rainDuration, boolean raining, boolean thundering) {
-diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java
-index dfa08dbf025ed702a864280a540e0169b9f33cbd..10fa6cec911950f72407ae7f45c8cf48caa9421a 100644
---- a/src/main/java/net/minecraft/server/level/TicketType.java
-+++ b/src/main/java/net/minecraft/server/level/TicketType.java
-@@ -8,6 +8,7 @@ import net.minecraft.world.level.ChunkPos;
-
- public class TicketType<T> {
- public static final TicketType<Long> FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper
-+ public static final TicketType<Long> ASYNC_LOAD = create("async_load", Long::compareTo); // Paper
-
- private final String name;
- private final Comparator<T> comparator;
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 4e7db441f68019d6e5d3359605b76bc4b258e87e..22c095539425a6667b8e7f5c5f0a8ff2e87adfb5 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -784,6 +784,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- this.disconnect(Component.translatable("disconnect.spam"));
- return;
- }
-+ // Paper start
-+ String str = packet.getCommand(); int index = -1;
-+ if (str.length() > 64 && ((index = str.indexOf(' ')) == -1 || index >= 64)) {
-+ server.scheduleOnMain(() -> this.disconnect(Component.translatable("disconnect.spam"))); // Paper
-+ return;
-+ }
-+ // Paper end
- // CraftBukkit end
- StringReader stringreader = new StringReader(packet.getCommand());
-
-diff --git a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
-index 821052e93ee753db6aaf499bbf39dc30598fe72f..2955c1ee153c410ea45fe367bac8597621c9bbd0 100644
---- a/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
-+++ b/src/main/java/net/minecraft/util/worldupdate/WorldUpgrader.java
-@@ -182,7 +182,11 @@ public class WorldUpgrader {
- }
-
- WorldUpgrader.LOGGER.error("Error upgrading chunk {}", chunkcoordintpair, throwable);
-+ // Paper start
-+ } catch (IOException e) {
-+ WorldUpgrader.LOGGER.error("Error upgrading chunk {}", chunkcoordintpair, e);
- }
-+ // Paper end
-
- if (flag1) {
- ++this.converted;
-diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-index db4fa7355b1f834d0f8a0710c1c583dded184613..ab9bb440c8e91ecb49c1e14a427d35087a87ac80 100644
---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-@@ -40,9 +40,11 @@ public class PoiManager extends SectionStorage<PoiSection> {
- public static final int VILLAGE_SECTION_SIZE = 1;
- private final PoiManager.DistanceTracker distanceTracker;
- private final LongSet loadedChunks = new LongOpenHashSet();
-+ private final net.minecraft.server.level.ServerLevel world; // Paper
-
- public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) {
- super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world);
-+ this.world = (net.minecraft.server.level.ServerLevel)world; // Paper
- this.distanceTracker = new PoiManager.DistanceTracker();
- }
-
-@@ -195,7 +197,18 @@ public class PoiManager extends SectionStorage<PoiSection> {
-
- @Override
- public void tick(BooleanSupplier shouldKeepTicking) {
-- super.tick(shouldKeepTicking);
-+ // Paper start - async chunk io
-+ while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean()) {
-+ ChunkPos chunkcoordintpair = SectionPos.of(this.dirty.firstLong()).chunk();
-+
-+ net.minecraft.nbt.CompoundTag data;
-+ try (co.aikar.timings.Timing ignored1 = this.world.timings.poiSaveDataSerialization.startTiming()) {
-+ data = this.getData(chunkcoordintpair);
-+ }
-+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world,
-+ chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY);
-+ }
-+ // Paper end
- this.distanceTracker.runAllUpdates();
- }
-
-@@ -288,6 +301,35 @@ public class PoiManager extends SectionStorage<PoiSection> {
- }
- }
-
-+ // Paper start - Asynchronous chunk io
-+ @javax.annotation.Nullable
-+ @Override
-+ public net.minecraft.nbt.CompoundTag read(ChunkPos chunkcoordintpair) throws java.io.IOException {
-+ if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) {
-+ net.minecraft.nbt.CompoundTag ret = com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE
-+ .loadChunkDataAsyncFuture(this.world, chunkcoordintpair.x, chunkcoordintpair.z, com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread(),
-+ true, false, true).join().poiData;
-+
-+ if (ret == com.destroystokyo.paper.io.PaperFileIOThread.FAILURE_VALUE) {
-+ throw new java.io.IOException("See logs for further detail");
-+ }
-+ return ret;
-+ }
-+ return super.read(chunkcoordintpair);
-+ }
-+
-+ @Override
-+ public void write(ChunkPos chunkcoordintpair, net.minecraft.nbt.CompoundTag nbttagcompound) throws java.io.IOException {
-+ if (this.world != null && Thread.currentThread() != com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE) {
-+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(
-+ this.world, chunkcoordintpair.x, chunkcoordintpair.z, nbttagcompound, null,
-+ com.destroystokyo.paper.io.IOUtil.getPriorityForCurrentThread());
-+ return;
-+ }
-+ super.write(chunkcoordintpair, nbttagcompound);
-+ }
-+ // Paper end
-+
- public static enum Occupancy {
- HAS_SPACE(PoiRecord::hasSpace),
- IS_OCCUPIED(PoiRecord::isOccupied),
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-index 864e2e0355a5fb8c1d4a5b0896ba299faf9ea534..8cc2a2c026eb44461cd94faeb64fb2151d2d3898 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-@@ -84,7 +84,31 @@ public class ChunkSerializer {
-
- public ChunkSerializer() {}
-
-+ // Paper start
-+ public static final class InProgressChunkHolder {
-+
-+ public final ProtoChunk protoChunk;
-+ public final java.util.ArrayDeque<Runnable> tasks;
-+
-+ public CompoundTag poiData;
-+
-+ public InProgressChunkHolder(final ProtoChunk protoChunk, final java.util.ArrayDeque<Runnable> tasks) {
-+ this.protoChunk = protoChunk;
-+ this.tasks = tasks;
-+ }
-+ }
-+ // Paper end
-+
- public static ProtoChunk read(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt) {
-+ // Paper start - add variant for async calls
-+ InProgressChunkHolder holder = loadChunk(world, poiStorage, chunkPos, nbt, true);
-+ holder.tasks.forEach(Runnable::run);
-+ return holder.protoChunk;
-+ }
-+
-+ public static InProgressChunkHolder loadChunk(ServerLevel world, PoiManager poiStorage, ChunkPos chunkPos, CompoundTag nbt, boolean distinguish) {
-+ java.util.ArrayDeque<Runnable> tasksToExecuteOnMain = new java.util.ArrayDeque<>();
-+ // Paper end
- ChunkPos chunkcoordintpair1 = new ChunkPos(nbt.getInt("xPos"), nbt.getInt("zPos"));
-
- if (!Objects.equals(chunkPos, chunkcoordintpair1)) {
-@@ -141,7 +165,9 @@ public class ChunkSerializer {
- LevelChunkSection chunksection = new LevelChunkSection(b0, datapaletteblock, (PalettedContainer) object); // CraftBukkit - read/write
-
- achunksection[k] = chunksection;
-+ tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main
- poiStorage.checkConsistencyWithBlocks(chunkPos, chunksection);
-+ }); // Paper - delay this task since we're executing off-main
- }
-
- boolean flag3 = nbttagcompound1.contains("BlockLight", 7);
-@@ -149,16 +175,28 @@ public class ChunkSerializer {
-
- if (flag3 || flag4) {
- if (!flag2) {
-+ tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main
- lightengine.retainData(chunkPos, true);
-+ }); // Paper - delay this task since we're executing off-main
- flag2 = true;
- }
-
- if (flag3) {
-- lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("BlockLight")), true);
-+ // Paper start - delay this task since we're executing off-main
-+ DataLayer blockLight = new DataLayer(nbttagcompound1.getByteArray("BlockLight").clone());
-+ tasksToExecuteOnMain.add(() -> {
-+ lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkPos, b0), blockLight, true);
-+ });
-+ // Paper end - delay this task since we're executing off-main
- }
-
- if (flag4) {
-- lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), new DataLayer(nbttagcompound1.getByteArray("SkyLight")), true);
-+ // Paper start - delay this task since we're executing off-main
-+ DataLayer skyLight = new DataLayer(nbttagcompound1.getByteArray("SkyLight").clone());
-+ tasksToExecuteOnMain.add(() -> {
-+ lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkPos, b0), skyLight, true);
-+ });
-+ // Paper end - delay this task since we're executing off-mai
- }
- }
- }
-@@ -278,7 +316,7 @@ public class ChunkSerializer {
- }
-
- if (chunkstatus_type == ChunkStatus.ChunkType.LEVELCHUNK) {
-- return new ImposterProtoChunk((LevelChunk) object1, false);
-+ return new InProgressChunkHolder(new ImposterProtoChunk((LevelChunk) object1, false), tasksToExecuteOnMain); // Paper - Async chunk loading
- } else {
- ProtoChunk protochunk1 = (ProtoChunk) object1;
-
-@@ -317,9 +355,67 @@ public class ChunkSerializer {
- protochunk1.setCarvingMask(worldgenstage_features, new CarvingMask(nbttagcompound4.getLongArray(s1), ((ChunkAccess) object1).getMinBuildHeight()));
- }
-
-- return protochunk1;
-+ return new InProgressChunkHolder(protochunk1, tasksToExecuteOnMain); // Paper - Async chunk loading
-+ }
-+ }
-+
-+ // Paper start - async chunk save for unload
-+ public record AsyncSaveData(
-+ DataLayer[] blockLight,
-+ DataLayer[] skyLight,
-+ Tag blockTickList, // non-null if we had to go to the server's tick list
-+ Tag fluidTickList, // non-null if we had to go to the server's tick list
-+ ListTag blockEntities,
-+ long worldTime
-+ ) {}
-+
-+ // must be called sync
-+ public static AsyncSaveData getAsyncSaveData(ServerLevel world, ChunkAccess chunk) {
-+ org.spigotmc.AsyncCatcher.catchOp("preparation of chunk data for async save");
-+ ChunkPos chunkPos = chunk.getPos();
-+
-+ ThreadedLevelLightEngine lightenginethreaded = world.getChunkSource().getLightEngine();
-+
-+ DataLayer[] blockLight = new DataLayer[lightenginethreaded.getMaxLightSection() - lightenginethreaded.getMinLightSection()];
-+ DataLayer[] skyLight = new DataLayer[lightenginethreaded.getMaxLightSection() - lightenginethreaded.getMinLightSection()];
-+
-+ for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) {
-+ DataLayer blockArray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkPos, i));
-+ DataLayer skyArray = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkPos, i));
-+
-+ // copy data for safety
-+ if (blockArray != null) {
-+ blockArray = blockArray.copy();
-+ }
-+ if (skyArray != null) {
-+ skyArray = skyArray.copy();
-+ }
-+
-+ blockLight[i - lightenginethreaded.getMinLightSection()] = blockArray;
-+ skyLight[i - lightenginethreaded.getMinLightSection()] = skyArray;
-+ }
-+
-+ final CompoundTag tickLists = new CompoundTag();
-+ ChunkSerializer.saveTicks(world, tickLists, chunk.getTicksForSerialization());
-+
-+ ListTag blockEntitiesSerialized = new ListTag();
-+ for (final BlockPos blockPos : chunk.getBlockEntitiesPos()) {
-+ final CompoundTag blockEntityNbt = chunk.getBlockEntityNbtForSaving(blockPos);
-+ if (blockEntityNbt != null) {
-+ blockEntitiesSerialized.add(blockEntityNbt);
-+ }
- }
-+
-+ return new AsyncSaveData(
-+ blockLight,
-+ skyLight,
-+ tickLists.get(BLOCK_TICKS_TAG),
-+ tickLists.get(FLUID_TICKS_TAG),
-+ blockEntitiesSerialized,
-+ world.getGameTime()
-+ );
- }
-+ // Paper end
-
- private static void logErrors(ChunkPos chunkPos, int y, String message) {
- ChunkSerializer.LOGGER.error("Recoverable errors when loading section [" + chunkPos.x + ", " + y + ", " + chunkPos.z + "]: " + message);
-@@ -336,6 +432,11 @@ public class ChunkSerializer {
- // CraftBukkit end
-
- public static CompoundTag write(ServerLevel world, ChunkAccess chunk) {
-+ // Paper start
-+ return saveChunk(world, chunk, null);
-+ }
-+ public static CompoundTag saveChunk(ServerLevel world, ChunkAccess chunk, @org.checkerframework.checker.nullness.qual.Nullable AsyncSaveData asyncsavedata) {
-+ // Paper end
- ChunkPos chunkcoordintpair = chunk.getPos();
- CompoundTag nbttagcompound = new CompoundTag();
-
-@@ -343,7 +444,7 @@ public class ChunkSerializer {
- nbttagcompound.putInt("xPos", chunkcoordintpair.x);
- nbttagcompound.putInt("yPos", chunk.getMinSection());
- nbttagcompound.putInt("zPos", chunkcoordintpair.z);
-- nbttagcompound.putLong("LastUpdate", world.getGameTime());
-+ nbttagcompound.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading
- nbttagcompound.putLong("InhabitedTime", chunk.getInhabitedTime());
- nbttagcompound.putString("Status", chunk.getStatus().getName());
- BlendingData blendingdata = chunk.getBlendingData();
-@@ -386,8 +487,17 @@ public class ChunkSerializer {
- for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) {
- int j = chunk.getSectionIndexFromSectionY(i);
- boolean flag1 = j >= 0 && j < achunksection.length;
-- DataLayer nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i));
-- DataLayer nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i));
-+ // Paper start - async chunk save for unload
-+ DataLayer nibblearray; // block light
-+ DataLayer nibblearray1; // sky light
-+ if (asyncsavedata == null) {
-+ nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); /// Paper - diff on method change (see getAsyncSaveData)
-+ nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); // Paper - diff on method change (see getAsyncSaveData)
-+ } else {
-+ nibblearray = asyncsavedata.blockLight[i - lightenginethreaded.getMinLightSection()];
-+ nibblearray1 = asyncsavedata.skyLight[i - lightenginethreaded.getMinLightSection()];
-+ }
-+ // Paper end
-
- if (flag1 || nibblearray != null || nibblearray1 != null) {
- CompoundTag nbttagcompound1 = new CompoundTag();
-@@ -425,8 +535,17 @@ public class ChunkSerializer {
- nbttagcompound.putBoolean("isLightOn", true);
- }
-
-- ListTag nbttaglist1 = new ListTag();
-- Iterator iterator = chunk.getBlockEntitiesPos().iterator();
-+ // Paper start
-+ ListTag nbttaglist1;
-+ Iterator<BlockPos> iterator;
-+ if (asyncsavedata != null) {
-+ nbttaglist1 = asyncsavedata.blockEntities;
-+ iterator = java.util.Collections.emptyIterator();
-+ } else {
-+ nbttaglist1 = new ListTag();
-+ iterator = chunk.getBlockEntitiesPos().iterator();
-+ }
-+ // Paper end
-
- CompoundTag nbttagcompound2;
-
-@@ -463,7 +582,14 @@ public class ChunkSerializer {
- nbttagcompound.put("CarvingMasks", nbttagcompound2);
- }
-
-+ // Paper start
-+ if (asyncsavedata != null) {
-+ nbttagcompound.put(BLOCK_TICKS_TAG, asyncsavedata.blockTickList);
-+ nbttagcompound.put(FLUID_TICKS_TAG, asyncsavedata.fluidTickList);
-+ } else {
- ChunkSerializer.saveTicks(world, nbttagcompound, chunk.getTicksForSerialization());
-+ }
-+ // Paper end
- nbttagcompound.put("PostProcessing", ChunkSerializer.packOffsets(chunk.getPostProcessing()));
- CompoundTag nbttagcompound3 = new CompoundTag();
- Iterator iterator1 = chunk.getHeightmaps().iterator();
-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 c56946f86565ad1ac41bb7b655c113f648d2f539..694778b5c23dbe9c8603c3483476b5252aa079bc 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
-@@ -28,26 +28,33 @@ import net.minecraft.world.level.storage.DimensionDataStorage;
- public class ChunkStorage implements AutoCloseable {
-
- public static final int LAST_MONOLYTH_STRUCTURE_DATA_VERSION = 1493;
-- private final IOWorker worker;
-+ // Paper - nuke IO worker
- protected final DataFixer fixerUpper;
- @Nullable
- private volatile LegacyStructureDataHandler legacyStructureHandler;
-+ // Paper start - async chunk loading
-+ private final Object persistentDataLock = new Object(); // Paper
-+ public final RegionFileStorage regionFileCache;
-+ // Paper end - async chunk loading
-
- public ChunkStorage(Path directory, DataFixer dataFixer, boolean dsync) {
- this.fixerUpper = dataFixer;
-- this.worker = new IOWorker(directory, dsync, "chunk");
-+ // Paper start - async chunk io
-+ // remove IO worker
-+ this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - nuke IOWorker
-+ // Paper end - async chunk io
- }
-
- public boolean isOldChunkAround(ChunkPos chunkPos, int checkRadius) {
-- return this.worker.isOldChunkAround(chunkPos, checkRadius);
-+ return true; // Paper - (for now, old unoptimised behavior) TODO implement later? the chunk status that blender uses SHOULD already have this radius loaded, no need to go back for it...
- }
-
- // CraftBukkit start
- private boolean check(ServerChunkCache cps, int x, int z) {
- ChunkPos pos = new ChunkPos(x, z);
- if (cps != null) {
-- com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread");
-- if (cps.hasChunk(x, z)) {
-+ //com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); // Paper - this function is now MT-Safe
-+ if (cps.getChunkAtIfCachedImmediately(x, z) != null) { // Paper - isLoaded is a ticket level check, not a chunk loaded check!
- return true;
- }
- }
-@@ -75,6 +82,7 @@ public class ChunkStorage implements AutoCloseable {
-
- public CompoundTag upgradeChunkTag(ResourceKey<LevelStem> resourcekey, Supplier<DimensionDataStorage> supplier, CompoundTag nbttagcompound, Optional<ResourceKey<Codec<? extends ChunkGenerator>>> optional, ChunkPos pos, @Nullable LevelAccessor generatoraccess) {
- // CraftBukkit end
-+ nbttagcompound = nbttagcompound.copy(); // Paper - defensive copy, another thread might modify this
- int i = ChunkStorage.getVersion(nbttagcompound);
-
- // CraftBukkit start
-@@ -92,9 +100,11 @@ public class ChunkStorage implements AutoCloseable {
- if (i < 1493) {
- nbttagcompound = NbtUtils.update(this.fixerUpper, DataFixTypes.CHUNK, nbttagcompound, i, 1493);
- if (nbttagcompound.getCompound("Level").getBoolean("hasLegacyStructureData")) {
-+ synchronized (this.persistentDataLock) { // Paper - Async chunk loading
- LegacyStructureDataHandler persistentstructurelegacy = this.getLegacyStructureHandler(resourcekey, supplier);
-
- nbttagcompound = persistentstructurelegacy.updateFromLegacy(nbttagcompound);
-+ } // Paper - Async chunk loading
- }
- }
-
-@@ -127,7 +137,7 @@ public class ChunkStorage implements AutoCloseable {
- LegacyStructureDataHandler persistentstructurelegacy = this.legacyStructureHandler;
-
- if (persistentstructurelegacy == null) {
-- synchronized (this) {
-+ synchronized (this.persistentDataLock) { // Paper - async chunk loading
- persistentstructurelegacy = this.legacyStructureHandler;
- if (persistentstructurelegacy == null) {
- this.legacyStructureHandler = persistentstructurelegacy = LegacyStructureDataHandler.getLegacyStructureHandler(resourcekey, (DimensionDataStorage) supplier.get());
-@@ -153,26 +163,49 @@ public class ChunkStorage implements AutoCloseable {
- }
-
- public CompletableFuture<Optional<CompoundTag>> read(ChunkPos chunkPos) {
-- return this.worker.loadAsync(chunkPos);
-+ // Paper start - async chunk io
-+ try {
-+ return CompletableFuture.completedFuture(Optional.ofNullable(this.readSync(chunkPos)));
-+ } catch (Throwable thr) {
-+ return CompletableFuture.failedFuture(thr);
-+ }
-+ }
-+ @Nullable
-+ public CompoundTag readSync(ChunkPos chunkPos) throws IOException {
-+ return this.regionFileCache.read(chunkPos);
- }
-+ // Paper end - async chunk io
-
-- public void write(ChunkPos chunkPos, CompoundTag nbt) {
-- this.worker.store(chunkPos, nbt);
-+ // Paper start - async chunk io
-+ public void write(ChunkPos chunkPos, CompoundTag nbt) throws IOException {
-+ this.regionFileCache.write(chunkPos, nbt);
-+ // Paper end - Async chunk loading
- if (this.legacyStructureHandler != null) {
-+ synchronized (this.persistentDataLock) { // Paper - Async chunk loading
- this.legacyStructureHandler.removeIndex(chunkPos.toLong());
-+ } // Paper - Async chunk loading
- }
-
- }
-
- public void flushWorker() {
-- this.worker.synchronize(true).join();
-+ com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.flush(); // Paper - nuke IO worker
- }
-
- public void close() throws IOException {
-- this.worker.close();
-+ this.regionFileCache.close(); // Paper - nuke IO worker
- }
-
- public ChunkScanAccess chunkScanner() {
-- return this.worker;
-+ // Paper start - nuke IO worker
-+ return ((chunkPos, streamTagVisitor) -> {
-+ try {
-+ this.regionFileCache.scanChunk(chunkPos, streamTagVisitor);
-+ return java.util.concurrent.CompletableFuture.completedFuture(null);
-+ } catch (IOException e) {
-+ throw new RuntimeException(e);
-+ }
-+ });
-+ // Paper end
- }
- }
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-index d51bafd2f5a763b8a49c835ab74a7cf60caa1ab6..7412da51c2eae70f17f4883f7223303d570c8402 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java
-@@ -44,6 +44,7 @@ public class RegionFile implements AutoCloseable {
- private final IntBuffer timestamps;
- @VisibleForTesting
- protected final RegionBitmap usedSectors;
-+ public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper
-
- public RegionFile(Path file, Path directory, boolean dsync) throws IOException {
- this(file, directory, RegionFileVersion.VERSION_DEFLATE, dsync);
-@@ -228,7 +229,7 @@ public class RegionFile implements AutoCloseable {
- return (byteCount + 4096 - 1) / 4096;
- }
-
-- public boolean doesChunkExist(ChunkPos pos) {
-+ public synchronized boolean doesChunkExist(ChunkPos pos) { // Paper - synchronized
- int i = this.getOffset(pos);
-
- if (i == 0) {
-@@ -393,6 +394,11 @@ public class RegionFile implements AutoCloseable {
- }
-
- public void close() throws IOException {
-+ // Paper start - Prevent regionfiles from being closed during use
-+ this.fileLock.lock();
-+ synchronized (this) {
-+ try {
-+ // Paper end
- try {
- this.padToFullSector();
- } finally {
-@@ -402,6 +408,10 @@ public class RegionFile implements AutoCloseable {
- this.file.close();
- }
- }
-+ } finally { // Paper start - Prevent regionfiles from being closed during use
-+ this.fileLock.unlock();
-+ }
-+ } // Paper end
-
- }
-
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-index 8ba1c073387fa21a20bd42a873ec3cc314eae64e..6fa0bc18ab05b9fb05521f46c5dadb695f1ec05b 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java
-@@ -29,11 +29,32 @@ public class RegionFileStorage implements AutoCloseable {
- this.sync = dsync;
- }
-
-- private RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit
-+ // Paper start
-+ public synchronized RegionFile getRegionFileIfLoaded(ChunkPos chunkcoordintpair) {
-+ return this.regionCache.getAndMoveToFirst(ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ()));
-+ }
-+
-+ public synchronized boolean chunkExists(ChunkPos pos) throws IOException {
-+ RegionFile regionfile = getRegionFile(pos, true);
-+
-+ return regionfile != null ? regionfile.hasChunk(pos) : false;
-+ }
-+
-+ public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly) throws IOException { // CraftBukkit
-+ return this.getRegionFile(chunkcoordintpair, existingOnly, false);
-+ }
-+ public synchronized RegionFile getRegionFile(ChunkPos chunkcoordintpair, boolean existingOnly, boolean lock) throws IOException {
-+ // Paper end
- long i = ChunkPos.asLong(chunkcoordintpair.getRegionX(), chunkcoordintpair.getRegionZ());
- RegionFile regionfile = (RegionFile) this.regionCache.getAndMoveToFirst(i);
-
- if (regionfile != null) {
-+ // Paper start
-+ if (lock) {
-+ // must be in this synchronized block
-+ regionfile.fileLock.lock();
-+ }
-+ // Paper end
- return regionfile;
- } else {
- if (this.regionCache.size() >= 256) {
-@@ -48,6 +69,12 @@ public class RegionFileStorage implements AutoCloseable {
- RegionFile regionfile1 = new RegionFile(path1, this.folder, this.sync);
-
- this.regionCache.putAndMoveToFirst(i, regionfile1);
-+ // Paper start
-+ if (lock) {
-+ // must be in this synchronized block
-+ regionfile1.fileLock.lock();
-+ }
-+ // Paper end
- return regionfile1;
- }
- }
-@@ -55,11 +82,12 @@ public class RegionFileStorage implements AutoCloseable {
- @Nullable
- public CompoundTag read(ChunkPos pos) throws IOException {
- // CraftBukkit start - SPIGOT-5680: There's no good reason to preemptively create files on read, save that for writing
-- RegionFile regionfile = this.getRegionFile(pos, true);
-+ RegionFile regionfile = this.getRegionFile(pos, true, true); // Paper
- if (regionfile == null) {
- return null;
- }
- // CraftBukkit end
-+ try { // Paper
- DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos);
-
- CompoundTag nbttagcompound;
-@@ -96,6 +124,9 @@ public class RegionFileStorage implements AutoCloseable {
- }
-
- return nbttagcompound;
-+ } finally { // Paper start
-+ regionfile.fileLock.unlock();
-+ } // Paper end
- }
-
- public void scanChunk(ChunkPos chunkcoordintpair, StreamTagVisitor streamtagvisitor) throws IOException {
-@@ -130,7 +161,8 @@ public class RegionFileStorage implements AutoCloseable {
- }
-
- protected void write(ChunkPos pos, @Nullable CompoundTag nbt) throws IOException {
-- RegionFile regionfile = this.getRegionFile(pos, false); // CraftBukkit
-+ RegionFile regionfile = this.getRegionFile(pos, false, true); // CraftBukkit // Paper
-+ try { // Paper
-
- if (nbt == null) {
- regionfile.clear(pos);
-@@ -156,9 +188,12 @@ public class RegionFileStorage implements AutoCloseable {
- }
- }
-
-+ } finally { // Paper start
-+ regionfile.fileLock.unlock();
-+ } // Paper end
- }
-
-- public void close() throws IOException {
-+ public synchronized void close() throws IOException { // Paper -> synchronized
- ExceptionCollector<IOException> exceptionsuppressor = new ExceptionCollector<>();
- ObjectIterator objectiterator = this.regionCache.values().iterator();
-
-@@ -175,7 +210,7 @@ public class RegionFileStorage implements AutoCloseable {
- exceptionsuppressor.throwIfPresent();
- }
-
-- public void flush() throws IOException {
-+ public synchronized void flush() throws IOException { // Paper - synchronize
- ObjectIterator objectiterator = this.regionCache.values().iterator();
-
- while (objectiterator.hasNext()) {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-index 8a4750dd8f604062c4ea452f7b97b05a0c8d583a..4dcfffe2e1c5263c3d1bd096d57d090c1e4b0523 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-@@ -34,10 +34,10 @@ import net.minecraft.world.level.ChunkPos;
- import net.minecraft.world.level.LevelHeightAccessor;
- import org.slf4j.Logger;
-
--public class SectionStorage<R> implements AutoCloseable {
-+public class SectionStorage<R> extends RegionFileStorage implements AutoCloseable { // Paper - nuke IOWorker
- private static final Logger LOGGER = LogUtils.getLogger();
- private static final String SECTIONS_TAG = "Sections";
-- private final IOWorker worker;
-+ // Paper - remove mojang I/O thread
- private final Long2ObjectMap<Optional<R>> storage = new Long2ObjectOpenHashMap<>();
- public final LongLinkedOpenHashSet dirty = new LongLinkedOpenHashSet();
- private final Function<Runnable, Codec<R>> codec;
-@@ -48,13 +48,14 @@ public class SectionStorage<R> implements AutoCloseable {
- protected final LevelHeightAccessor levelHeightAccessor;
-
- public SectionStorage(Path path, Function<Runnable, Codec<R>> codecFactory, Function<Runnable, R> factory, DataFixer dataFixer, DataFixTypes dataFixTypes, boolean dsync, RegistryAccess dynamicRegistryManager, LevelHeightAccessor world) {
-+ super(path, dsync); // Paper - remove mojang I/O thread
- this.codec = codecFactory;
- this.factory = factory;
- this.fixerUpper = dataFixer;
- this.type = dataFixTypes;
- this.registryAccess = dynamicRegistryManager;
- this.levelHeightAccessor = world;
-- this.worker = new IOWorker(path, dsync, path.getFileName().toString());
-+ // Paper - remove mojang I/O thread
- }
-
- protected void tick(BooleanSupplier shouldKeepTicking) {
-@@ -122,15 +123,20 @@ public class SectionStorage<R> implements AutoCloseable {
- }
-
- private CompletableFuture<Optional<CompoundTag>> tryRead(ChunkPos pos) {
-- return this.worker.loadAsync(pos).exceptionally((throwable) -> {
-- if (throwable instanceof IOException iOException) {
-- LOGGER.error("Error reading chunk {} data from disk", pos, iOException);
-- return Optional.empty();
-- } else {
-- throw new CompletionException(throwable);
-- }
-- });
-+ // Paper start - async chunk io
-+ try {
-+ return CompletableFuture.completedFuture(Optional.ofNullable(this.read(pos)));
-+ } catch (Throwable thr) {
-+ return CompletableFuture.failedFuture(thr);
-+ }
-+ // Paper end - async chunk io
-+ }
-+
-+ // Paper start - async chunk io
-+ public void loadInData(ChunkPos chunkPos, CompoundTag compound) {
-+ this.readColumn(chunkPos, RegistryOps.create(NbtOps.INSTANCE, this.registryAccess), compound);
- }
-+ // Paper end - aync chnnk i
-
- private <T> void readColumn(ChunkPos pos, DynamicOps<T> ops, @Nullable T data) {
- if (data == null) {
-@@ -170,7 +176,7 @@ public class SectionStorage<R> implements AutoCloseable {
- Dynamic<Tag> dynamic = this.writeColumn(pos, registryOps);
- Tag tag = dynamic.getValue();
- if (tag instanceof CompoundTag) {
-- this.worker.store(pos, (CompoundTag)tag);
-+ try { this.write(pos, (CompoundTag)tag); } catch (IOException ioexception) { SectionStorage.LOGGER.error("Error writing data to disk", ioexception); } // Paper - nuke IOWorker
- } else {
- LOGGER.error("Expected compound tag, got {}", (Object)tag);
- }
-@@ -198,6 +204,21 @@ public class SectionStorage<R> implements AutoCloseable {
- return new Dynamic<>(ops, ops.createMap(ImmutableMap.of(ops.createString("Sections"), ops.createMap(map), ops.createString("DataVersion"), ops.createInt(SharedConstants.getCurrentVersion().getWorldVersion()))));
- }
-
-+ // Paper start - internal get data function, copied from above
-+ private CompoundTag getDataInternal(ChunkPos pos) {
-+ RegistryOps<Tag> registryOps = RegistryOps.create(NbtOps.INSTANCE, this.registryAccess);
-+ Dynamic<Tag> dynamic = this.writeColumn(pos, registryOps);
-+ Tag nbtbase = (Tag) dynamic.getValue();
-+
-+ if (nbtbase instanceof CompoundTag) {
-+ return (CompoundTag)nbtbase;
-+ } else {
-+ SectionStorage.LOGGER.error("Expected compound tag, got {}", nbtbase);
-+ }
-+ return null;
-+ }
-+ // Paper end
-+
- private static long getKey(ChunkPos chunkPos, int y) {
- return SectionPos.asLong(chunkPos.x, y, chunkPos.z);
- }
-@@ -233,6 +254,24 @@ public class SectionStorage<R> implements AutoCloseable {
-
- @Override
- public void close() throws IOException {
-- this.worker.close();
-+ //this.worker.close(); // Paper - nuke I/O worker - don't call the worker
-+ super.close(); // Paper - nuke I/O worker - call super.close method which is responsible for closing used files.
-+ }
-+
-+ // Paper start - get data function
-+ public CompoundTag getData(ChunkPos chunkcoordintpair) {
-+ // Note: Copied from above
-+ // This is checking if the data needs to be written, then it builds it later in getDataInternal(ChunkCoordIntPair)
-+ if (!this.dirty.isEmpty()) {
-+ for (int i = this.levelHeightAccessor.getMinSection(); i < this.levelHeightAccessor.getMaxSection(); ++i) {
-+ long j = SectionPos.of(chunkcoordintpair, i).asLong();
-+
-+ if (this.dirty.contains(j)) {
-+ return this.getDataInternal(chunkcoordintpair);
-+ }
-+ }
-+ }
-+ return null;
- }
-+ // Paper end
- }
-diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
-index 335120afc88a8fc1543c2e6df516fd728e3ab032..581cde7a74e00bee1ce69086132d5f871d206399 100644
---- a/src/main/java/org/spigotmc/WatchdogThread.java
-+++ b/src/main/java/org/spigotmc/WatchdogThread.java
-@@ -83,6 +83,7 @@ public class WatchdogThread extends Thread
- //
- log.log( Level.SEVERE, "------------------------------" );
- log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Spigot!):" );
-+ com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper
- WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log );
- log.log( Level.SEVERE, "------------------------------" );
- //
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0020-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/patches/removed/1.19.2-legacy-chunksystem/0020-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch
deleted file mode 100644
index ec8ccf7a1d..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0020-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch
+++ /dev/null
@@ -1,1254 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 11 Apr 2020 03:56:07 -0400
-Subject: [PATCH] Implement Chunk Priority / Urgency System for Chunks
-
-Mark chunks that are blocking main thread for world generation as urgent
-
-Implements a general priority system so that chunks that are sorted in
-the generator queues can prioritize certain chunks over another.
-
-Urgent chunks will jump to the front of the line, ensuring that a
-sync chunk load on an ungenerated chunk does not lag the server for
-a long period of time if the servers generator queues are filled with
-lots of chunks already.
-
-This massively reduces the lag spikes from sync chunk gens.
-
-Then we further prioritize loading order so nearby chunks have higher
-priority than distant chunks, reducing the pressure a high no tick
-view distance holds on you.
-
-Chunks in front of the player have higher priority, to help with
-fast traveling players keep up with their movement.
-
-diff --git a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java
-index af40e473521f408aa0e112953c43bdbce164a48b..68860a3b6db2aa50373d71aec9502c18d48ab8b9 100644
---- a/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java
-+++ b/src/main/java/com/destroystokyo/paper/io/chunk/ChunkTaskManager.java
-@@ -107,7 +107,7 @@ public final class ChunkTaskManager {
- }
-
- static void dumpChunkInfo(Set<ChunkHolder> seenChunks, ChunkHolder chunkHolder, int x, int z) {
-- dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 1);
-+ dumpChunkInfo(seenChunks, chunkHolder, x, z, 0, 4); // Paper - 1->4
- }
-
- static void dumpChunkInfo(Set<ChunkHolder> seenChunks, ChunkHolder chunkHolder, int x, int z, int indent, int maxDepth) {
-@@ -128,6 +128,31 @@ public final class ChunkTaskManager {
- PaperFileIOThread.LOGGER.error(indentStr + "Chunk Status - " + ((chunk == null) ? "null chunk" : chunk.getStatus().toString()));
- PaperFileIOThread.LOGGER.error(indentStr + "Chunk Ticket Status - " + ChunkHolder.getStatus(chunkHolder.getTicketLevel()));
- PaperFileIOThread.LOGGER.error(indentStr + "Chunk Holder Status - " + ((holderStatus == null) ? "null" : holderStatus.toString()));
-+ // Paper start
-+ PaperFileIOThread.LOGGER.error(indentStr + "Chunk Holder Priority - " + chunkHolder.queueLevel);
-+
-+ if (!chunkHolder.neighbors.isEmpty()) {
-+ if (indent >= maxDepth) {
-+ PaperFileIOThread.LOGGER.error(indentStr + "Chunk Neighbors: (Can't show, too deeply nested)");
-+ return;
-+ }
-+ PaperFileIOThread.LOGGER.error(indentStr + "Chunk Neighbors: ");
-+ for (ChunkHolder neighbor : chunkHolder.neighbors.keySet()) {
-+ ChunkStatus status = neighbor.getChunkHolderStatus();
-+ if (status != null && status.isOrAfter(ChunkHolder.getStatus(neighbor.getTicketLevel()))) {
-+ continue;
-+ }
-+ int nx = neighbor.pos.x;
-+ int nz = neighbor.pos.z;
-+ if (seenChunks.contains(neighbor)) {
-+ PaperFileIOThread.LOGGER.error(indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + " (CIRCULAR)");
-+ continue;
-+ }
-+ PaperFileIOThread.LOGGER.error(indentStr + " " + nx + "," + nz + " in " + chunkHolder.getWorld().getWorld().getName() + ":");
-+ dumpChunkInfo(seenChunks, neighbor, nx, nz, indent + 1, maxDepth);
-+ }
-+ }
-+ // Paper end
- }
- }
-
-diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java
-index 7f76c304f5eb3c2f27b348918588ab67b795b1ba..1b1bfd5f92f85f46ad9661a0a64a2a1b4c33a80d 100644
---- a/src/main/java/net/minecraft/server/ChunkSystem.java
-+++ b/src/main/java/net/minecraft/server/ChunkSystem.java
-@@ -55,6 +55,19 @@ public final class ChunkSystem {
-
- static final TicketType<Long> CHUNK_LOAD = TicketType.create("chunk_load", Long::compareTo);
-
-+ // Paper start - priority
-+ private static int getPriorityBoost(final PrioritisedExecutor.Priority priority) {
-+ if (priority.isLowerOrEqualPriority(PrioritisedExecutor.Priority.NORMAL)) {
-+ return 0;
-+ }
-+
-+ int dist = PrioritisedExecutor.Priority.BLOCKING.ordinal() - PrioritisedExecutor.Priority.NORMAL.ordinal();
-+
-+
-+ return (net.minecraft.server.level.DistanceManager.URGENT_PRIORITY * (priority.ordinal() - PrioritisedExecutor.Priority.NORMAL.ordinal())) / dist;
-+ }
-+ // Paper end - priority
-+
- private static long chunkLoadCounter = 0L;
- public static void scheduleChunkLoad(final ServerLevel level, final int chunkX, final int chunkZ, final ChunkStatus toStatus,
- final boolean addTicket, final PrioritisedExecutor.Priority priority, final Consumer<ChunkAccess> onComplete) {
-@@ -68,12 +81,19 @@ public final class ChunkSystem {
- final int minLevel = 33 + ChunkStatus.getDistance(toStatus);
- final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
- final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
-+ final int priorityBoost = getPriorityBoost(priority);
-
- if (addTicket) {
- level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
- }
- level.chunkSource.runDistanceManagerUpdates();
-
-+ if (priorityBoost == net.minecraft.server.level.DistanceManager.URGENT_PRIORITY) {
-+ level.chunkSource.markUrgent(chunkPos);
-+ } else if (priorityBoost != 0) {
-+ level.chunkSource.markHighPriority(chunkPos, priorityBoost);
-+ }
-+
- final Consumer<ChunkAccess> loadCallback = (final ChunkAccess chunk) -> {
- try {
- if (onComplete != null) {
-@@ -89,6 +109,11 @@ public final class ChunkSystem {
- level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
- level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
- }
-+ if (priorityBoost == net.minecraft.server.level.DistanceManager.URGENT_PRIORITY) {
-+ level.chunkSource.clearUrgent(chunkPos);
-+ } else if (priorityBoost != 0) {
-+ level.chunkSource.clearPriorityTickets(chunkPos);
-+ }
- }
- };
-
-@@ -135,12 +160,17 @@ public final class ChunkSystem {
- final int radius = toStatus.ordinal() - 1;
- final Long chunkReference = addTicket ? Long.valueOf(++chunkLoadCounter) : null;
- final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
-+ final int priorityBoost = getPriorityBoost(priority);
-
- if (addTicket) {
- level.chunkSource.addTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
- }
- level.chunkSource.runDistanceManagerUpdates();
-
-+ if (priorityBoost != 0) {
-+ level.chunkSource.markAreaHighPriority(chunkPos, priorityBoost, radius);
-+ }
-+
- final Consumer<LevelChunk> loadCallback = (final LevelChunk chunk) -> {
- try {
- if (onComplete != null) {
-@@ -156,6 +186,9 @@ public final class ChunkSystem {
- level.chunkSource.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, minLevel, chunkPos);
- level.chunkSource.removeTicketAtLevel(CHUNK_LOAD, chunkPos, minLevel, chunkReference);
- }
-+ if (priorityBoost != 0) {
-+ level.chunkSource.clearAreaPriorityTickets(chunkPos, radius);
-+ }
- }
- };
-
-diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
-index 2e56c52e3ee45b0304a9e6a5eab863ef96b2aab0..5eb6ce20ee17d87db0f6c2dcee96d6d0891d6c50 100644
---- a/src/main/java/net/minecraft/server/MCUtil.java
-+++ b/src/main/java/net/minecraft/server/MCUtil.java
-@@ -634,6 +634,7 @@ public final class MCUtil {
- chunkData.addProperty("x", playerChunk.pos.x);
- chunkData.addProperty("z", playerChunk.pos.z);
- chunkData.addProperty("ticket-level", playerChunk.getTicketLevel());
-+ chunkData.addProperty("priority", playerChunk.queueLevel); // Paper - priority
- chunkData.addProperty("state", ChunkHolder.getFullChunkStatus(playerChunk.getTicketLevel()).toString());
- chunkData.addProperty("queued-for-unload", chunkMap.toDrop.contains(playerChunk.pos.longKey));
- chunkData.addProperty("status", status == null ? "unloaded" : status.toString());
-diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index e30893d6cbe3b42338d04453d0f452babeb61d8a..a52932d665ca45a5e066d7cef0ec0313d1c3f69f 100644
---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -60,7 +60,7 @@ public class ChunkHolder {
- private final DebugBuffer<ChunkHolder.ChunkSaveDebug> chunkToSaveHistory;
- public int oldTicketLevel;
- private int ticketLevel;
-- private int queueLevel;
-+ public volatile int queueLevel; // Paper - private->public, make volatile since this is concurrently accessed
- public final ChunkPos pos;
- private boolean hasChangedSections;
- private final ShortSet[] changedBlocksPerSection;
-@@ -73,6 +73,7 @@ public class ChunkHolder {
- private boolean resendLight;
- private CompletableFuture<Void> pendingFullStateConfirmation;
-
-+ public ServerLevel getWorld() { return chunkMap.level; } // Paper
- boolean isUpdateQueued = false; // Paper
- private final ChunkMap chunkMap; // Paper
-
-@@ -438,12 +439,18 @@ public class ChunkHolder {
- });
- }
-
-+ // Paper start
-+ private boolean loadCallbackScheduled = false;
-+ private boolean unloadCallbackScheduled = false;
-+ // Paper end
-+
- private void demoteFullChunk(ChunkMap playerchunkmap, ChunkHolder.FullChunkStatus playerchunk_state) {
- this.pendingFullStateConfirmation.cancel(false);
- playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state);
- }
-
- protected void updateFutures(ChunkMap chunkStorage, Executor executor) {
-+ io.papermc.paper.util.TickThread.ensureTickThread("Async ticket level update"); // Paper
- ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel);
- ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel);
- boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE;
-@@ -454,9 +461,22 @@ public class ChunkHolder {
- // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins.
- if (playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
-+ io.papermc.paper.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Paper
- LevelChunk chunk = (LevelChunk)either.left().orElse(null);
-- if (chunk != null) {
-+ if (chunk != null && chunk.wasLoadCallbackInvoked() && ChunkHolder.this.ticketLevel > 33) { // Paper - only invoke unload if load was called
-+ // Paper start - only schedule once, now the future is no longer completed as RIGHT if unloaded...
-+ if (ChunkHolder.this.unloadCallbackScheduled) {
-+ return;
-+ }
-+ ChunkHolder.this.unloadCallbackScheduled = true;
-+ // Paper end - only schedule once, now the future is no longer completed as RIGHT if unloaded...
- chunkStorage.callbackExecutor.execute(() -> {
-+ // Paper start - only schedule once, now the future is no longer completed as RIGHT if unloaded...
-+ ChunkHolder.this.unloadCallbackScheduled = false;
-+ if (ChunkHolder.this.ticketLevel <= 33) {
-+ return;
-+ }
-+ // Paper end - only schedule once, now the future is no longer completed as RIGHT if unloaded...
- // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick
- // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag.
- // These actions may however happen deferred, so we manually set the needsSaving flag already here.
-@@ -501,11 +521,13 @@ public class ChunkHolder {
- this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, ChunkHolder.FullChunkStatus.BORDER);
- // Paper start - cache ticking ready status
- this.fullChunkFuture.thenAccept(either -> {
-+ io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper
- final Optional<LevelChunk> left = either.left();
- if (left.isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) {
- LevelChunk fullChunk = either.left().get();
- ChunkHolder.this.isFullChunkReady = true;
- net.minecraft.server.ChunkSystem.onChunkBorder(fullChunk, this);
-+ this.chunkMap.distanceManager.clearPriorityTickets(pos); // Paper - chunk priority
- }
- });
- this.updateChunkToSave(this.fullChunkFuture, "full");
-@@ -531,6 +553,7 @@ public class ChunkHolder {
- this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, ChunkHolder.FullChunkStatus.TICKING);
- // Paper start - cache ticking ready status
- this.tickingChunkFuture.thenAccept(either -> {
-+ io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper
- either.ifLeft(chunk -> {
- // note: Here is a very good place to add callbacks to logic waiting on this.
- ChunkHolder.this.isTickingReady = true;
-@@ -563,6 +586,7 @@ public class ChunkHolder {
- this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, ChunkHolder.FullChunkStatus.ENTITY_TICKING);
- // Paper start - cache ticking ready status
- this.entityTickingChunkFuture.thenAccept(either -> {
-+ io.papermc.paper.util.TickThread.ensureTickThread("Async full chunk future completion"); // Paper
- either.ifLeft(chunk -> {
- ChunkHolder.this.isEntityTickingReady = true;
- net.minecraft.server.ChunkSystem.onChunkEntityTicking(chunk, this);
-@@ -586,16 +610,45 @@ public class ChunkHolder {
- this.demoteFullChunk(chunkStorage, playerchunk_state1);
- }
-
-- this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
-+ //this.onLevelChange.onLevelChange(this.pos, this::getQueueLevel, this.ticketLevel, this::setQueueLevel);
-+ // Paper start - raise IO/load priority if priority changes, use our preferred priority
-+ priorityBoost = chunkMap.distanceManager.getChunkPriority(pos);
-+ int currRequestedPriority = this.requestedPriority;
-+ int priority = getDemandedPriority();
-+ int newRequestedPriority = this.requestedPriority = priority;
-+ if (this.queueLevel > priority) {
-+ int ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY;
-+ if (priority <= 10) {
-+ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY;
-+ } else if (priority <= 20) {
-+ ioPriority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY;
-+ }
-+ chunkMap.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, ioPriority);
-+ chunkMap.level.getChunkSource().getLightEngine().queue.changePriority(pos.toLong(), this.queueLevel, priority); // Paper // Restore this in chunk priority later?
-+ }
-+ if (currRequestedPriority != newRequestedPriority) {
-+ this.onLevelChange.onLevelChange(this.pos, () -> this.queueLevel, priority, p -> this.queueLevel = p); // use preferred priority
-+ int neighborsPriority = getNeighborsPriority();
-+ this.neighbors.forEach((neighbor, neighborDesired) -> neighbor.setNeighborPriority(this, neighborsPriority));
-+ }
-+ // Paper end
- this.oldTicketLevel = this.ticketLevel;
- // CraftBukkit start
- // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins.
- if (!playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
- this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
-+ io.papermc.paper.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Paper
- LevelChunk chunk = (LevelChunk)either.left().orElse(null);
-- if (chunk != null) {
-+ if (chunk != null && ChunkHolder.this.oldTicketLevel <= 33 && !chunk.wasLoadCallbackInvoked()) { // Paper - ensure ticket level is set to loaded before calling, as now this can complete with ticket level > 33
-+ // Paper start - only schedule once, now the future is no longer completed as RIGHT if unloaded...
-+ if (ChunkHolder.this.loadCallbackScheduled) {
-+ return;
-+ }
-+ ChunkHolder.this.loadCallbackScheduled = true;
-+ // Paper end - only schedule once, now the future is no longer completed as RIGHT if unloaded...
- chunkStorage.callbackExecutor.execute(() -> {
-- chunk.loadCallback();
-+ ChunkHolder.this.loadCallbackScheduled = false; // Paper - only schedule once, now the future is no longer completed as RIGHT if unloaded...
-+ if (ChunkHolder.this.oldTicketLevel <= 33) chunk.loadCallback(); // Paper "
- });
- }
- }).exceptionally((throwable) -> {
-@@ -696,7 +749,134 @@ public class ChunkHolder {
- };
- }
-
-- // Paper start
-+ // Paper start - Chunk gen/load priority system
-+ volatile int neighborPriority = -1;
-+ volatile int priorityBoost = 0;
-+ public final java.util.concurrent.ConcurrentHashMap<ChunkHolder, ChunkStatus> neighbors = new java.util.concurrent.ConcurrentHashMap<>();
-+ public final it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<Integer> neighborPriorities = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
-+ int requestedPriority = ChunkMap.MAX_CHUNK_DISTANCE + 1; // this priority is possible pending, but is used to ensure needless updates are not queued
-+
-+ private int getDemandedPriority() {
-+ int priority = neighborPriority; // if we have a neighbor priority, use it
-+ int myPriority = getMyPriority();
-+
-+ if (priority == -1 || (ticketLevel <= 33 && priority > myPriority)) {
-+ priority = myPriority;
-+ }
-+
-+ return Math.max(1, Math.min(Math.max(ticketLevel, ChunkMap.MAX_CHUNK_DISTANCE), priority));
-+ }
-+
-+ private int getMyPriority() {
-+ if (priorityBoost == DistanceManager.URGENT_PRIORITY) {
-+ return 2; // Urgent - ticket level isn't always 31 so 33-30 = 3, but allow 1 more tasks to go below this for dependents
-+ }
-+ return ticketLevel - priorityBoost;
-+ }
-+
-+ private int getNeighborsPriority() {
-+ return (neighborPriorities.isEmpty() ? getMyPriority() : getDemandedPriority()) + 1;
-+ }
-+
-+ public void onNeighborRequest(ChunkHolder neighbor, ChunkStatus status) {
-+ neighbor.setNeighborPriority(this, getNeighborsPriority());
-+ this.neighbors.compute(neighbor, (playerChunk, currentWantedStatus) -> {
-+ if (currentWantedStatus == null || !currentWantedStatus.isOrAfter(status)) {
-+ //System.out.println(this + " request " + neighbor + " at " + status + " currently " + currentWantedStatus);
-+ return status;
-+ } else {
-+ //System.out.println(this + " requested " + neighbor + " at " + status + " but thats lower than other wanted status " + currentWantedStatus);
-+ return currentWantedStatus;
-+ }
-+ });
-+
-+ }
-+
-+ public void onNeighborDone(ChunkHolder neighbor, ChunkStatus chunkstatus, ChunkAccess chunk) {
-+ this.neighbors.compute(neighbor, (playerChunk, wantedStatus) -> {
-+ if (wantedStatus != null && chunkstatus.isOrAfter(wantedStatus)) {
-+ //System.out.println(this + " neighbor done at " + neighbor + " for status " + chunkstatus + " wanted " + wantedStatus);
-+ neighbor.removeNeighborPriority(this);
-+ return null;
-+ } else {
-+ //System.out.println(this + " neighbor finished our previous request at " + neighbor + " for status " + chunkstatus + " but we now want instead " + wantedStatus);
-+ return wantedStatus;
-+ }
-+ });
-+ }
-+
-+ private void removeNeighborPriority(ChunkHolder requester) {
-+ synchronized (neighborPriorities) {
-+ neighborPriorities.remove(requester.pos.toLong());
-+ recalcNeighborPriority();
-+ }
-+ checkPriority();
-+ }
-+
-+
-+ private void setNeighborPriority(ChunkHolder requester, int priority) {
-+ synchronized (neighborPriorities) {
-+ if (!Integer.valueOf(priority).equals(neighborPriorities.put(requester.pos.toLong(), Integer.valueOf(priority)))) {
-+ recalcNeighborPriority();
-+ }
-+ }
-+ checkPriority();
-+ }
-+
-+ private void recalcNeighborPriority() {
-+ neighborPriority = -1;
-+ if (!neighborPriorities.isEmpty()) {
-+ synchronized (neighborPriorities) {
-+ for (Integer neighbor : neighborPriorities.values()) {
-+ if (neighbor < neighborPriority || neighborPriority == -1) {
-+ neighborPriority = neighbor;
-+ }
-+ }
-+ }
-+ }
-+ }
-+ private void checkPriority() {
-+ if (this.requestedPriority != getDemandedPriority()) this.chunkMap.queueHolderUpdate(this);
-+ }
-+
-+ public final double getDistance(ServerPlayer player) {
-+ return getDistance(player.getX(), player.getZ());
-+ }
-+ public final double getDistance(double blockX, double blockZ) {
-+ int cx = net.minecraft.server.MCUtil.fastFloor(blockX) >> 4;
-+ int cz = net.minecraft.server.MCUtil.fastFloor(blockZ) >> 4;
-+ final double x = pos.x - cx;
-+ final double z = pos.z - cz;
-+ return (x * x) + (z * z);
-+ }
-+
-+ public final double getDistanceFrom(BlockPos pos) {
-+ return getDistance(pos.getX(), pos.getZ());
-+ }
-+
-+ public static ChunkStatus getNextStatus(ChunkStatus status) {
-+ if (status == ChunkStatus.FULL) {
-+ return status;
-+ }
-+ return CHUNK_STATUSES.get(status.getIndex() + 1);
-+ }
-+ public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getStatusFutureUncheckedMain(ChunkStatus chunkstatus) {
-+ return ensureMain(getFutureIfPresentUnchecked(chunkstatus));
-+ }
-+ public <T> CompletableFuture<T> ensureMain(CompletableFuture<T> future) {
-+ return future.thenApplyAsync(r -> r, chunkMap.mainInvokingExecutor);
-+ }
-+
-+ @Override
-+ public String toString() {
-+ return "PlayerChunk{" +
-+ "location=" + pos +
-+ ", ticketLevel=" + ticketLevel + "/" + getStatus(this.ticketLevel) +
-+ ", chunkHolderStatus=" + getChunkHolderStatus() +
-+ ", neighborPriority=" + getNeighborsPriority() +
-+ ", priority=(" + ticketLevel + " - " + priorityBoost +" vs N " + neighborPriority + ") = " + getDemandedPriority() + " A " + queueLevel +
-+ '}';
-+ }
- public final boolean isEntityTickingReady() {
- return this.isEntityTickingReady;
- }
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index c3bbaf32373a32417f8b83f386f8cf327c6e0893..46bfaf04867d913c1782d851de101d913376c63a 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -131,6 +131,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- public final ServerLevel level;
- private final ThreadedLevelLightEngine lightEngine;
- private final BlockableEventLoop<Runnable> mainThreadExecutor;
-+ final java.util.concurrent.Executor mainInvokingExecutor; // Paper
- public ChunkGenerator generator;
- private RandomState randomState;
- public final Supplier<DimensionDataStorage> overworldDataStorage;
-@@ -267,6 +268,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
-
- this.mainThreadExecutor = mainThreadExecutor;
-+ // Paper start
-+ this.mainInvokingExecutor = (run) -> {
-+ if (MCUtil.isMainThread()) {
-+ run.run();
-+ } else {
-+ mainThreadExecutor.execute(run);
-+ }
-+ };
-+ // Paper end
- ProcessorMailbox<Runnable> threadedmailbox = ProcessorMailbox.create(executor, "worldgen");
-
- Objects.requireNonNull(mainThreadExecutor);
-@@ -309,6 +319,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- });
- }
-
-+ // Paper start - Chunk Prioritization
-+ public void queueHolderUpdate(ChunkHolder playerchunk) {
-+ Runnable runnable = () -> {
-+ if (isUnloading(playerchunk)) {
-+ return; // unloaded
-+ }
-+ distanceManager.pendingChunkUpdates.add(playerchunk);
-+ if (!distanceManager.pollingPendingChunkUpdates) {
-+ level.getChunkSource().runDistanceManagerUpdates();
-+ }
-+ };
-+ if (MCUtil.isMainThread()) {
-+ // We can't use executor here because it will not execute tasks if its currently in the middle of executing tasks...
-+ runnable.run();
-+ } else {
-+ mainThreadExecutor.execute(runnable);
-+ }
-+ }
-+
-+ private boolean isUnloading(ChunkHolder playerchunk) {
-+ return playerchunk == null || toDrop.contains(playerchunk.pos.toLong());
-+ }
-+
-+ private void updateChunkPriorityMap(it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap map, long chunk, int level) {
-+ int prev = map.getOrDefault(chunk, -1);
-+ if (level > prev) {
-+ map.put(chunk, level);
-+ }
-+ }
-+ // Paper end
-+
- private static double euclideanDistanceSquared(ChunkPos pos, Entity entity) {
- double d0 = (double) SectionPos.sectionToBlockCoord(pos.x, 8);
- double d1 = (double) SectionPos.sectionToBlockCoord(pos.z, 8);
-@@ -399,6 +440,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- List<ChunkHolder> list1 = new ArrayList();
- int j = centerChunk.x;
- int k = centerChunk.z;
-+ ChunkHolder requestingNeighbor = getUpdatingChunkIfPresent(centerChunk.toLong()); // Paper
-
- for (int l = -margin; l <= margin; ++l) {
- for (int i1 = -margin; i1 <= margin; ++i1) {
-@@ -417,6 +459,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- ChunkStatus chunkstatus = (ChunkStatus) distanceToStatus.apply(j1);
- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = playerchunk.getOrScheduleFuture(chunkstatus, this);
-+ // Paper start
-+ if (requestingNeighbor != null && requestingNeighbor != playerchunk && !completablefuture.isDone()) {
-+ requestingNeighbor.onNeighborRequest(playerchunk, chunkstatus);
-+ completablefuture.thenAccept(either -> {
-+ requestingNeighbor.onNeighborDone(playerchunk, chunkstatus, either.left().orElse(null));
-+ });
-+ }
-+ // Paper end
-
- list1.add(playerchunk);
- list.add(completablefuture);
-@@ -733,11 +783,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- if (requiredStatus == ChunkStatus.EMPTY) {
- return this.scheduleChunkLoad(chunkcoordintpair);
- } else {
-+ // Paper start - revert 1.17 chunk system changes
-+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = holder.getOrScheduleFuture(requiredStatus.getParent(), this);
-+ return future.thenComposeAsync((either) -> {
-+ Optional<ChunkAccess> optional = either.left();
-+ if (!optional.isPresent()) {
-+ return CompletableFuture.completedFuture(either);
-+ }
-+ // Paper end - revert 1.17 chunk system changes
- if (requiredStatus == ChunkStatus.LIGHT) {
- this.distanceManager.addTicket(TicketType.LIGHT, chunkcoordintpair, 33 + ChunkStatus.getDistance(ChunkStatus.LIGHT), chunkcoordintpair);
- }
-
-- Optional<ChunkAccess> optional = ((Either) holder.getOrScheduleFuture(requiredStatus.getParent(), this).getNow(ChunkHolder.UNLOADED_CHUNK)).left();
-+ // Paper - revert 1.17 chunk system changes
-
- if (optional.isPresent() && ((ChunkAccess) optional.get()).getStatus().isOrAfter(requiredStatus)) {
- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> completablefuture = requiredStatus.load(this.level, this.structureTemplateManager, this.lightEngine, (ichunkaccess) -> {
-@@ -749,6 +807,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- } else {
- return this.scheduleChunkGeneration(holder, requiredStatus);
- }
-+ }, this.mainThreadExecutor).thenComposeAsync(CompletableFuture::completedFuture, this.mainThreadExecutor); // Paper - revert 1.17 chunk system changes
- }
- }
-
-@@ -788,14 +847,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- };
-
- CompletableFuture<CompoundTag> chunkSaveFuture = this.level.asyncChunkTaskManager.getChunkSaveFuture(pos.x, pos.z);
-+ // Paper start
-+ ChunkHolder playerChunk = getUpdatingChunkIfPresent(pos.toLong());
-+ int chunkPriority = playerChunk != null ? playerChunk.requestedPriority : 33;
-+ int priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY;
-+
-+ if (chunkPriority <= 10) {
-+ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY;
-+ } else if (chunkPriority <= 20) {
-+ priority = com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY;
-+ }
-+ boolean isHighestPriority = priority == com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY;
-+ // Paper end
- if (chunkSaveFuture != null) {
-- this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z,
-- com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY, chunkHolderConsumer, false, chunkSaveFuture);
-- this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGH_PRIORITY);
-+ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, priority, chunkHolderConsumer, isHighestPriority, chunkSaveFuture); // Paper
- } else {
-- this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z,
-- com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, chunkHolderConsumer, false);
-+ this.level.asyncChunkTaskManager.scheduleChunkLoad(pos.x, pos.z, priority, chunkHolderConsumer, isHighestPriority); // Paper
- }
-+ this.level.asyncChunkTaskManager.raisePriority(pos.x, pos.z, priority); // Paper
- return ret;
- // Paper end - Async chunk io
- }
-@@ -874,7 +943,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- this.releaseLightTicket(chunkcoordintpair);
- return CompletableFuture.completedFuture(Either.right(playerchunk_failure));
- });
-- }, executor);
-+ }, executor).thenComposeAsync((either) -> { // Paper start - force competion on the main thread
-+ return CompletableFuture.completedFuture(either);
-+ }, this.mainThreadExecutor); // use the main executor, we want to ensure only one chunk callback can be completed per runnable execute
-+ // Paper end - force competion on the main thread
- }
-
- protected void releaseLightTicket(ChunkPos pos) {
-@@ -957,7 +1029,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- long i = chunkHolder.getPos().toLong();
-
- Objects.requireNonNull(chunkHolder);
-- mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, chunkHolder::getTicketLevel));
-+ mailbox.tell(ChunkTaskPriorityQueueSorter.message(runnable, i, () -> 1)); // Paper - final loads are always urgent!
- });
- }
-
-diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index 1d6ab658c48bb765f66624f276ec7b05cf33c1d5..b9b56068cdacd984f873cfb2a06a312e9912893d 100644
---- a/src/main/java/net/minecraft/server/level/DistanceManager.java
-+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -114,6 +114,7 @@ public abstract class DistanceManager {
- }
-
- private static int getTicketLevelAt(SortedArraySet<Ticket<?>> tickets) {
-+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::getTicketLevelAt"); // Paper
- return !tickets.isEmpty() ? ((Ticket) tickets.first()).getTicketLevel() : ChunkMap.MAX_CHUNK_DISTANCE + 1;
- }
-
-@@ -128,6 +129,7 @@ public abstract class DistanceManager {
- public boolean runAllUpdates(ChunkMap chunkStorage) {
- this.naturalSpawnChunkCounter.runAllUpdates();
- this.tickingTicketsTracker.runAllUpdates();
-+ org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
- this.playerTicketManager.runAllUpdates();
- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
- boolean flag = i != 0;
-@@ -138,11 +140,13 @@ public abstract class DistanceManager {
-
- // Paper start
- if (!this.pendingChunkUpdates.isEmpty()) {
-+ this.pollingPendingChunkUpdates = true; try { // Paper - Chunk priority
- while(!this.pendingChunkUpdates.isEmpty()) {
- ChunkHolder remove = this.pendingChunkUpdates.remove();
- remove.isUpdateQueued = false;
- remove.updateFutures(chunkStorage, this.mainThreadExecutor);
- }
-+ } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority
- // Paper end
- return true;
- } else {
-@@ -178,8 +182,10 @@ public abstract class DistanceManager {
- return flag;
- }
- }
-+ boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority
-
- boolean addTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
-+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addTicket"); // Paper
- SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
- int j = DistanceManager.getTicketLevelAt(arraysetsorted);
- Ticket<?> ticket1 = (Ticket) arraysetsorted.addOrGet(ticket);
-@@ -193,7 +199,9 @@ public abstract class DistanceManager {
- }
-
- boolean removeTicket(long i, Ticket<?> ticket) { // CraftBukkit - void -> boolean
-+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::removeTicket"); // Paper
- SortedArraySet<Ticket<?>> arraysetsorted = this.getTickets(i);
-+ int oldLevel = getTicketLevelAt(arraysetsorted); // Paper
-
- boolean removed = false; // CraftBukkit
- if (arraysetsorted.remove(ticket)) {
-@@ -225,7 +233,12 @@ public abstract class DistanceManager {
- this.tickets.remove(i);
- }
-
-- this.ticketTracker.update(i, DistanceManager.getTicketLevelAt(arraysetsorted), false);
-+ // Paper start - Chunk priority
-+ int newLevel = getTicketLevelAt(arraysetsorted);
-+ if (newLevel > oldLevel) {
-+ this.ticketTracker.update(i, newLevel, false);
-+ }
-+ // Paper end
- return removed; // CraftBukkit
- }
-
-@@ -275,6 +288,112 @@ public abstract class DistanceManager {
- });
- }
-
-+ // Paper start - Chunk priority
-+ public static final int PRIORITY_TICKET_LEVEL = ChunkMap.MAX_CHUNK_DISTANCE;
-+ public static final int URGENT_PRIORITY = 29;
-+ public boolean delayDistanceManagerTick = false;
-+ public boolean markUrgent(ChunkPos coords) {
-+ return addPriorityTicket(coords, TicketType.URGENT, URGENT_PRIORITY);
-+ }
-+ public boolean markHighPriority(ChunkPos coords, int priority) {
-+ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority));
-+ return addPriorityTicket(coords, TicketType.PRIORITY, priority);
-+ }
-+
-+ public void markAreaHighPriority(ChunkPos center, int priority, int radius) {
-+ delayDistanceManagerTick = true;
-+ priority = Math.min(URGENT_PRIORITY - 1, Math.max(1, priority));
-+ int finalPriority = priority;
-+ net.minecraft.server.MCUtil.getSpiralOutChunks(center.getWorldPosition(), radius).forEach(coords -> {
-+ addPriorityTicket(coords, TicketType.PRIORITY, finalPriority);
-+ });
-+ delayDistanceManagerTick = false;
-+ chunkMap.level.getChunkSource().runDistanceManagerUpdates();
-+ }
-+
-+ public void clearAreaPriorityTickets(ChunkPos center, int radius) {
-+ delayDistanceManagerTick = true;
-+ net.minecraft.server.MCUtil.getSpiralOutChunks(center.getWorldPosition(), radius).forEach(coords -> {
-+ this.removeTicket(coords.toLong(), new Ticket<ChunkPos>(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords));
-+ });
-+ delayDistanceManagerTick = false;
-+ chunkMap.level.getChunkSource().runDistanceManagerUpdates();
-+ }
-+
-+ private boolean addPriorityTicket(ChunkPos coords, TicketType<ChunkPos> ticketType, int priority) {
-+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket");
-+ long pair = coords.toLong();
-+ ChunkHolder chunk = chunkMap.getUpdatingChunkIfPresent(pair);
-+ if ((chunk != null && chunk.isFullChunkReady())) {
-+ return false;
-+ }
-+
-+ boolean success;
-+ if (!(success = updatePriorityTicket(coords, ticketType, priority))) {
-+ Ticket<ChunkPos> ticket = new Ticket<ChunkPos>(ticketType, PRIORITY_TICKET_LEVEL, coords);
-+ ticket.priority = priority;
-+ success = this.addTicket(pair, ticket);
-+ } else {
-+ if (chunk == null) {
-+ chunk = chunkMap.getUpdatingChunkIfPresent(pair);
-+ }
-+ chunkMap.queueHolderUpdate(chunk);
-+ }
-+
-+ //chunkMap.world.getWorld().spawnParticle(priority <= 15 ? org.bukkit.Particle.EXPLOSION_HUGE : org.bukkit.Particle.EXPLOSION_NORMAL, chunkMap.world.getWorld().getPlayers(), null, coords.x << 4, 70, coords.z << 4, 2, 0, 0, 0, 1, null, true);
-+
-+ chunkMap.level.getChunkSource().runDistanceManagerUpdates();
-+
-+ return success;
-+ }
-+
-+ private boolean updatePriorityTicket(ChunkPos coords, TicketType<ChunkPos> type, int priority) {
-+ SortedArraySet<Ticket<?>> tickets = this.tickets.get(coords.toLong());
-+ if (tickets == null) {
-+ return false;
-+ }
-+ for (Ticket<?> ticket : tickets) {
-+ if (ticket.getType() == type) {
-+ // We only support increasing, not decreasing, too complicated
-+ ticket.setCreatedTick(this.ticketTickCounter);
-+ ticket.priority = Math.max(ticket.priority, priority);
-+ return true;
-+ }
-+ }
-+
-+ return false;
-+ }
-+
-+ public int getChunkPriority(ChunkPos coords) {
-+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::getChunkPriority");
-+ SortedArraySet<Ticket<?>> tickets = this.tickets.get(coords.toLong());
-+ if (tickets == null) {
-+ return 0;
-+ }
-+ for (Ticket<?> ticket : tickets) {
-+ if (ticket.getType() == TicketType.URGENT) {
-+ return URGENT_PRIORITY;
-+ }
-+ }
-+ for (Ticket<?> ticket : tickets) {
-+ if (ticket.getType() == TicketType.PRIORITY && ticket.priority > 0) {
-+ return ticket.priority;
-+ }
-+ }
-+ return 0;
-+ }
-+
-+ public void clearPriorityTickets(ChunkPos coords) {
-+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::clearPriority");
-+ this.removeTicket(coords.toLong(), new Ticket<ChunkPos>(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords));
-+ }
-+
-+ public void clearUrgent(ChunkPos coords) {
-+ org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::clearUrgent");
-+ this.removeTicket(coords.toLong(), new Ticket<ChunkPos>(TicketType.URGENT, PRIORITY_TICKET_LEVEL, coords));
-+ }
-+ // Paper end
-+
- protected void updateChunkForced(ChunkPos pos, boolean forced) {
- Ticket<ChunkPos> ticket = new Ticket<>(TicketType.FORCED, 31, pos);
- long i = pos.toLong();
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 07671ac54f598872dba2b22ec8f82db3dd037d7f..5057053bcd3fc205e62edd9519a9545c16ce60c7 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -409,6 +409,30 @@ public class ServerChunkCache extends ChunkSource {
-
- return ret;
- }
-+
-+ public boolean markUrgent(ChunkPos coords) {
-+ return this.distanceManager.markUrgent(coords);
-+ }
-+
-+ public boolean markHighPriority(ChunkPos coords, int priority) {
-+ return this.distanceManager.markHighPriority(coords, priority);
-+ }
-+
-+ public void markAreaHighPriority(ChunkPos center, int priority, int radius) {
-+ this.distanceManager.markAreaHighPriority(center, priority, radius);
-+ }
-+
-+ public void clearAreaPriorityTickets(ChunkPos center, int radius) {
-+ this.distanceManager.clearAreaPriorityTickets(center, radius);
-+ }
-+
-+ public void clearPriorityTickets(ChunkPos coords) {
-+ this.distanceManager.clearPriorityTickets(coords);
-+ }
-+
-+ public void clearUrgent(ChunkPos coords) {
-+ this.distanceManager.clearUrgent(coords);
-+ }
- // Paper end - async chunk io
-
- @Nullable
-@@ -443,6 +467,8 @@ public class ServerChunkCache extends ChunkSource {
- Objects.requireNonNull(completablefuture);
- if (!completablefuture.isDone()) { // Paper
- // Paper start - async chunk io/loading
-+ ChunkPos pair = new ChunkPos(x1, z1); // Paper - Chunk priority
-+ this.distanceManager.markUrgent(pair); // Paper - Chunk priority
- this.level.asyncChunkTaskManager.raisePriority(x1, z1, com.destroystokyo.paper.io.PrioritizedTaskQueue.HIGHEST_PRIORITY);
- com.destroystokyo.paper.io.chunk.ChunkTaskManager.pushChunkWait(this.level, x1, z1);
- // Paper end
-@@ -450,6 +476,8 @@ public class ServerChunkCache extends ChunkSource {
- chunkproviderserver_b.managedBlock(completablefuture::isDone);
- com.destroystokyo.paper.io.chunk.ChunkTaskManager.popChunkWait(); // Paper - async chunk debug
- this.level.timings.syncChunkLoad.stopTiming(); // Paper
-+ this.distanceManager.clearPriorityTickets(pair); // Paper - Chunk priority
-+ this.distanceManager.clearUrgent(pair); // Paper - Chunk priority
- } // Paper
- ichunkaccess = (ChunkAccess) ((Either) completablefuture.join()).map((ichunkaccess1) -> {
- return ichunkaccess1;
-@@ -555,10 +583,12 @@ public class ServerChunkCache extends ChunkSource {
- if (create && !currentlyUnloading) {
- // CraftBukkit end
- this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
-+ if (isUrgent) this.distanceManager.markUrgent(chunkcoordintpair); // Paper - Chunk priority
- if (this.chunkAbsent(playerchunk, l)) {
- ProfilerFiller gameprofilerfiller = this.level.getProfiler();
-
- gameprofilerfiller.push("chunkLoad");
-+ distanceManager.delayDistanceManagerTick = false; // Paper - Chunk priority - ensure this is never false
- this.runDistanceManagerUpdates();
- playerchunk = this.getVisibleChunkIfPresent(k);
- gameprofilerfiller.pop();
-@@ -568,7 +598,13 @@ public class ServerChunkCache extends ChunkSource {
- }
- }
-
-- return this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap);
-+ // Paper start - Chunk priority
-+ CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap);
-+ if (isUrgent) {
-+ future.thenAccept(either -> this.distanceManager.clearUrgent(chunkcoordintpair));
-+ }
-+ return future;
-+ // Paper end
- }
-
- private boolean chunkAbsent(@Nullable ChunkHolder holder, int maxLevel) {
-@@ -620,6 +656,7 @@ public class ServerChunkCache extends ChunkSource {
- }
-
- public boolean runDistanceManagerUpdates() {
-+ if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority
- boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
- boolean flag1 = this.chunkMap.promoteChunkMap();
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index 9ab4588e4e512176b881ad4c252e400ff6ea97bd..4adf2d503015cac85b12fbaae833b33eeeb44403 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -191,6 +191,7 @@ public class ServerPlayer extends Player {
- private int lastRecordedArmor = Integer.MIN_VALUE;
- private int lastRecordedLevel = Integer.MIN_VALUE;
- private int lastRecordedExperience = Integer.MIN_VALUE;
-+ public boolean isRealPlayer; // Paper - chunk priority
- private float lastSentHealth = -1.0E8F;
- private int lastSentFood = -99999999;
- private boolean lastFoodSaturationZero = true;
-@@ -318,6 +319,21 @@ public class ServerPlayer extends Player {
- this.bukkitPickUpLoot = true;
- this.maxHealthCache = this.getMaxHealth();
- }
-+ // Paper start - Chunk priority
-+ public BlockPos getPointInFront(double inFront) {
-+ double rads = Math.toRadians(net.minecraft.server.MCUtil.normalizeYaw(this.yRot + 90)); // MC rotates yaw 90 for some odd reason
-+ final double x = getX() + inFront * Math.cos(rads);
-+ final double z = getZ() + inFront * Math.sin(rads);
-+ return new BlockPos(x, getY(), z);
-+ }
-+
-+ public ChunkPos getChunkInFront(double inFront) {
-+ double rads = Math.toRadians(net.minecraft.server.MCUtil.normalizeYaw(this.yRot + 90)); // MC rotates yaw 90 for some odd reason
-+ final double x = getX() + (inFront * 16) * Math.cos(rads);
-+ final double z = getZ() + (inFront * 16) * Math.sin(rads);
-+ return new ChunkPos(Mth.floor(x) >> 4, Mth.floor(z) >> 4);
-+ }
-+ // Paper end
-
- // Yes, this doesn't match Vanilla, but it's the best we can do for now.
- // If this is an issue, PRs are welcome
-diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
-index 5b238e41ffa3e374b52ee955cb39087571c6ffc2..5539f2a7e069cbe98997b734f3b1cd498148f09b 100644
---- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
-+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java
-@@ -26,15 +26,140 @@ import org.slf4j.Logger;
- public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable {
- private static final Logger LOGGER = LogUtils.getLogger();
- private final ProcessorMailbox<Runnable> taskMailbox;
-- private final ObjectList<Pair<ThreadedLevelLightEngine.TaskType, Runnable>> lightTasks = new ObjectArrayList<>();
-- private final ChunkMap chunkMap;
-+ // Paper start
-+ private static final int MAX_PRIORITIES = ChunkMap.MAX_CHUNK_DISTANCE + 2;
-+
-+ static class ChunkLightQueue {
-+ public boolean shouldFastUpdate;
-+ java.util.ArrayDeque<Runnable> pre = new java.util.ArrayDeque<Runnable>();
-+ java.util.ArrayDeque<Runnable> post = new java.util.ArrayDeque<Runnable>();
-+
-+ ChunkLightQueue(long chunk) {}
-+ }
-+
-+ static class PendingLightTask {
-+ long chunkId;
-+ IntSupplier priority;
-+ Runnable pre;
-+ Runnable post;
-+ boolean fastUpdate;
-+
-+ public PendingLightTask(long chunkId, IntSupplier priority, Runnable pre, Runnable post, boolean fastUpdate) {
-+ this.chunkId = chunkId;
-+ this.priority = priority;
-+ this.pre = pre;
-+ this.post = post;
-+ this.fastUpdate = fastUpdate;
-+ }
-+ }
-+
-+
-+ // Retain the chunks priority level for queued light tasks
-+ class LightQueue {
-+ private int size = 0;
-+ private final it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<ChunkLightQueue>[] buckets = new it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap[MAX_PRIORITIES];
-+ private final java.util.concurrent.ConcurrentLinkedQueue<PendingLightTask> pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>();
-+ private final java.util.concurrent.ConcurrentLinkedQueue<Runnable> priorityChanges = new java.util.concurrent.ConcurrentLinkedQueue<>();
-+
-+ private LightQueue() {
-+ for (int i = 0; i < buckets.length; i++) {
-+ buckets[i] = new it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<>();
-+ }
-+ }
-+
-+ public void changePriority(long pair, int currentPriority, int priority) {
-+ this.priorityChanges.add(() -> {
-+ ChunkLightQueue remove = this.buckets[currentPriority].remove(pair);
-+ if (remove != null) {
-+ ChunkLightQueue existing = this.buckets[Math.max(1, priority)].put(pair, remove);
-+ if (existing != null) {
-+ remove.pre.addAll(existing.pre);
-+ remove.post.addAll(existing.post);
-+ }
-+ }
-+ });
-+ }
-+
-+ public final void addChunk(long chunkId, IntSupplier priority, Runnable pre, Runnable post) {
-+ pendingTasks.add(new PendingLightTask(chunkId, priority, pre, post, true));
-+ tryScheduleUpdate();
-+ }
-+
-+ public final void add(long chunkId, IntSupplier priority, ThreadedLevelLightEngine.TaskType type, Runnable run) {
-+ pendingTasks.add(new PendingLightTask(chunkId, priority, type == TaskType.PRE_UPDATE ? run : null, type == TaskType.POST_UPDATE ? run : null, false));
-+ }
-+ public final void add(PendingLightTask update) {
-+ int priority = update.priority.getAsInt();
-+ ChunkLightQueue lightQueue = this.buckets[priority].computeIfAbsent(update.chunkId, ChunkLightQueue::new);
-+
-+ if (update.pre != null) {
-+ this.size++;
-+ lightQueue.pre.add(update.pre);
-+ }
-+ if (update.post != null) {
-+ this.size++;
-+ lightQueue.post.add(update.post);
-+ }
-+ if (update.fastUpdate) {
-+ lightQueue.shouldFastUpdate = true;
-+ }
-+ }
-+
-+ public final boolean isEmpty() {
-+ return this.size == 0 && this.pendingTasks.isEmpty();
-+ }
-+
-+ public final int size() {
-+ return this.size;
-+ }
-+
-+ public boolean poll(java.util.List<Runnable> pre, java.util.List<Runnable> post) {
-+ PendingLightTask pending;
-+ while ((pending = pendingTasks.poll()) != null) {
-+ add(pending);
-+ }
-+ Runnable run;
-+ while ((run = priorityChanges.poll()) != null) {
-+ run.run();
-+ }
-+ boolean hasWork = false;
-+ it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<ChunkLightQueue>[] buckets = this.buckets;
-+ int priority = 0;
-+ while (priority < MAX_PRIORITIES && !isEmpty()) {
-+ it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap<ChunkLightQueue> bucket = buckets[priority];
-+ if (bucket.isEmpty()) {
-+ priority++;
-+ if (hasWork) {
-+ return true;
-+ } else {
-+ continue;
-+ }
-+ }
-+ ChunkLightQueue queue = bucket.removeFirst();
-+ this.size -= queue.pre.size() + queue.post.size();
-+ pre.addAll(queue.pre);
-+ post.addAll(queue.post);
-+ queue.pre.clear();
-+ queue.post.clear();
-+ hasWork = true;
-+ if (queue.shouldFastUpdate) {
-+ return true;
-+ }
-+ }
-+ return hasWork;
-+ }
-+ }
-+
-+ final LightQueue queue = new LightQueue();
-+ // Paper end
-+ private final ChunkMap chunkMap; private final ChunkMap playerChunkMap; // Paper
- private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> sorterMailbox;
- private volatile int taskPerBatch = 5;
- private final AtomicBoolean scheduled = new AtomicBoolean();
-
- public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox<Runnable> processor, ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> executor) {
- super(chunkProvider, true, hasBlockLight);
-- this.chunkMap = chunkStorage;
-+ this.chunkMap = chunkStorage; this.playerChunkMap = chunkMap; // Paper
- this.sorterMailbox = executor;
- this.taskMailbox = processor;
- }
-@@ -120,13 +245,9 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
- }
-
- private void addTask(int x, int z, IntSupplier completedLevelSupplier, ThreadedLevelLightEngine.TaskType stage, Runnable task) {
-- this.sorterMailbox.tell(ChunkTaskPriorityQueueSorter.message(() -> {
-- this.lightTasks.add(Pair.of(stage, task));
-- if (this.lightTasks.size() >= this.taskPerBatch) {
-- this.runUpdate();
-- }
--
-- }, ChunkPos.asLong(x, z), completedLevelSupplier));
-+ // Paper start - replace method
-+ this.queue.add(ChunkPos.asLong(x, z), completedLevelSupplier, stage, task);
-+ // Paper end
- }
-
- @Override
-@@ -154,8 +275,14 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
-
- public CompletableFuture<ChunkAccess> lightChunk(ChunkAccess chunk, boolean excludeBlocks) {
- ChunkPos chunkPos = chunk.getPos();
-- chunk.setLightCorrect(false);
-- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> {
-+ // Paper start
-+ //ichunkaccess.b(false); // Don't need to disable this
-+ long pair = chunkPos.toLong();
-+ CompletableFuture<ChunkAccess> future = new CompletableFuture<>();
-+ IntSupplier prioritySupplier = playerChunkMap.getChunkQueueLevel(pair);
-+ boolean[] skippedPre = {false};
-+ this.queue.addChunk(pair, prioritySupplier, Util.name(() -> {
-+ // Paper end
- LevelChunkSection[] levelChunkSections = chunk.getSections();
-
- for(int i = 0; i < chunk.getSectionsCount(); ++i) {
-@@ -175,51 +302,45 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl
-
- }, () -> {
- return "lightChunk " + chunkPos + " " + excludeBlocks;
-- }));
-- return CompletableFuture.supplyAsync(() -> {
-+ // Paper start - merge the 2 together
-+ }), () -> {
-+ this.chunkMap.releaseLightTicket(chunkPos); // Paper - moved from below, we want to call this even when returning early
-+ if (skippedPre[0]) return; // Paper - future's already complete
- chunk.setLightCorrect(true);
- super.retainData(chunkPos, false);
-- this.chunkMap.releaseLightTicket(chunkPos);
-- return chunk;
-- }, (runnable) -> {
-- this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.POST_UPDATE, runnable);
-+ //this.chunkMap.releaseLightTicket(chunkPos); // Paper - moved up
-+ future.complete(chunk);
- });
-+ return future;
-+ // Paper end
- }
-
- public void tryScheduleUpdate() {
-- if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) {
-+ if ((!this.queue.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { // Paper
- this.taskMailbox.tell(() -> {
- this.runUpdate();
- this.scheduled.set(false);
-+ tryScheduleUpdate(); // Paper - if we still have work to do, do it!
- });
- }
-
- }
-
-+ // Paper start - replace impl
-+ private final java.util.List<Runnable> pre = new java.util.ArrayList<>();
-+ private final java.util.List<Runnable> post = new java.util.ArrayList<>();
- private void runUpdate() {
-- int i = Math.min(this.lightTasks.size(), this.taskPerBatch);
-- ObjectListIterator<Pair<ThreadedLevelLightEngine.TaskType, Runnable>> objectListIterator = this.lightTasks.iterator();
--
-- int j;
-- for(j = 0; objectListIterator.hasNext() && j < i; ++j) {
-- Pair<ThreadedLevelLightEngine.TaskType, Runnable> pair = objectListIterator.next();
-- if (pair.getFirst() == ThreadedLevelLightEngine.TaskType.PRE_UPDATE) {
-- pair.getSecond().run();
-- }
-+ if (queue.poll(pre, post)) {
-+ pre.forEach(Runnable::run);
-+ pre.clear();
-+ super.runUpdates(Integer.MAX_VALUE, true, true);
-+ post.forEach(Runnable::run);
-+ post.clear();
-+ } else {
-+ // might have level updates to go still
-+ super.runUpdates(Integer.MAX_VALUE, true, true);
- }
--
-- objectListIterator.back(j);
-- super.runUpdates(Integer.MAX_VALUE, true, true);
--
-- for(int var5 = 0; objectListIterator.hasNext() && var5 < i; ++var5) {
-- Pair<ThreadedLevelLightEngine.TaskType, Runnable> pair2 = objectListIterator.next();
-- if (pair2.getFirst() == ThreadedLevelLightEngine.TaskType.POST_UPDATE) {
-- pair2.getSecond().run();
-- }
--
-- objectListIterator.remove();
-- }
--
-+ // Paper end
- }
-
- public void setTaskPerBatch(int taskBatchSize) {
-diff --git a/src/main/java/net/minecraft/server/level/Ticket.java b/src/main/java/net/minecraft/server/level/Ticket.java
-index f1128f0d4a9a0241ac6c9bc18dd13b431c616bb1..2b2b7851d5f68bcdb41d58bcc64740ba58bf1ef4 100644
---- a/src/main/java/net/minecraft/server/level/Ticket.java
-+++ b/src/main/java/net/minecraft/server/level/Ticket.java
-@@ -8,6 +8,7 @@ public final class Ticket<T> implements Comparable<Ticket<?>> {
- public final T key;
- public long createdTick;
- public long delayUnloadBy; // Paper
-+ public int priority; // Paper - Chunk priority
-
- protected Ticket(TicketType<T> type, int level, T argument) {
- this.type = type;
-diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java
-index 10fa6cec911950f72407ae7f45c8cf48caa9421a..478109526cff7ceb0565cea3b5e97b9a07fbf8d1 100644
---- a/src/main/java/net/minecraft/server/level/TicketType.java
-+++ b/src/main/java/net/minecraft/server/level/TicketType.java
-@@ -9,6 +9,8 @@ import net.minecraft.world.level.ChunkPos;
- public class TicketType<T> {
- public static final TicketType<Long> FUTURE_AWAIT = create("future_await", Long::compareTo); // Paper
- public static final TicketType<Long> ASYNC_LOAD = create("async_load", Long::compareTo); // Paper
-+ public static final TicketType<ChunkPos> PRIORITY = create("priority", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper
-+ public static final TicketType<ChunkPos> URGENT = create("urgent", Comparator.comparingLong(ChunkPos::toLong), 300); // Paper
-
- private final String name;
- private final Comparator<T> comparator;
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 5833cc3d5014dad82607afc4d643b6bed885be64..8e0f73dcef189442450b4518437fb3a1c34b9a47 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -177,6 +177,7 @@ public abstract class PlayerList {
- }
-
- public void placeNewPlayer(Connection connection, ServerPlayer player) {
-+ player.isRealPlayer = true; // Paper - Chunk priority
- GameProfile gameprofile = player.getGameProfile();
- GameProfileCache usercache = this.server.getProfileCache();
- Optional<GameProfile> optional = usercache.get(gameprofile.getId());
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 1a44c98b69398ba5dcb4115f0e8fdcf3f62fd920..9878aded49d0049b066fa608c7eaf25a55fcf12e 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -212,7 +212,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- private BlockPos blockPosition;
- private ChunkPos chunkPosition;
- private Vec3 deltaMovement;
-- private float yRot;
-+ public float yRot; // Paper - private->public
- private float xRot;
- public float yRotO;
- public float xRotO;
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 58a245b2ca6e65d491694142ad04d38236b46434..893051059df51133a127b0870e27ab67461052fa 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -134,7 +134,7 @@ public class LevelChunk extends ChunkAccess {
- return NEIGHBOUR_CACHE_RADIUS;
- }
-
-- boolean loadedTicketLevel;
-+ boolean loadedTicketLevel; public final boolean wasLoadCallbackInvoked() { return this.loadedTicketLevel; } // Paper - public accessor
- private long neighbourChunksLoadedBitset;
- private final LevelChunk[] loadedNeighbourChunks = new LevelChunk[(NEIGHBOUR_CACHE_RADIUS * 2 + 1) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)];
-
-@@ -653,6 +653,7 @@ public class LevelChunk extends ChunkAccess {
-
- // CraftBukkit start
- public void loadCallback() {
-+ if (this.loadedTicketLevel) { LOGGER.error("Double calling chunk load!", new Throwable()); } // Paper
- // Paper start - neighbour cache
- int chunkX = this.chunkPos.x;
- int chunkZ = this.chunkPos.z;
-@@ -707,6 +708,7 @@ public class LevelChunk extends ChunkAccess {
- }
-
- public void unloadCallback() {
-+ if (!this.loadedTicketLevel) { LOGGER.error("Double calling chunk unload!", new Throwable()); } // Paper
- org.bukkit.Server server = this.level.getCraftServer();
- org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(this.bukkitChunk, this.isUnsaved());
- server.getPluginManager().callEvent(unloadEvent);
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0048-Per-Player-View-Distance-API-placeholders.patch b/patches/removed/1.19.2-legacy-chunksystem/0048-Per-Player-View-Distance-API-placeholders.patch
deleted file mode 100644
index 05d14292f2..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0048-Per-Player-View-Distance-API-placeholders.patch
+++ /dev/null
@@ -1,112 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Zach Brown <[email protected]>
-Date: Mon, 6 May 2019 01:29:25 -0400
-Subject: [PATCH] Per-Player View Distance API placeholders
-
-I hope to look at this more in-depth soon. It appears doable.
-However this should not block the update.
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index db7e2207612b56b0869a947edd03a6d3f9209e22..981e60c7bf2eee52e84f9894ff689631388a7715 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -2253,4 +2253,6 @@ public class ServerPlayer extends Player {
- return (CraftPlayer) super.getBukkitEntity();
- }
- // CraftBukkit end
-+
-+ public final int getViewDistance() { return this.getLevel().getChunkSource().chunkMap.viewDistance - 1; } // Paper - placeholder
- }
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 9a6820b10e4164cc38d269853b5c2a49175cb890..0ed46cdd443ac42a7d57ee59f6f04fd9e9259c16 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -1913,6 +1913,37 @@ public class CraftWorld extends CraftRegionAccessor implements World {
- return world.spigotConfig.simulationDistance;
- }
- // Spigot end
-+ // Paper start - view distance api
-+ @Override
-+ public void setViewDistance(int viewDistance) {
-+ throw new UnsupportedOperationException(); //TODO
-+ }
-+
-+ @Override
-+ public void setSimulationDistance(int simulationDistance) {
-+ throw new UnsupportedOperationException(); //TODO
-+ }
-+
-+ @Override
-+ public int getNoTickViewDistance() {
-+ throw new UnsupportedOperationException(); //TODO
-+ }
-+
-+ @Override
-+ public void setNoTickViewDistance(int viewDistance) {
-+ throw new UnsupportedOperationException(); //TODO
-+ }
-+
-+ @Override
-+ public int getSendViewDistance() {
-+ throw new UnsupportedOperationException(); //TODO
-+ }
-+
-+ @Override
-+ public void setSendViewDistance(int viewDistance) {
-+ throw new UnsupportedOperationException(); //TODO
-+ }
-+ // Paper end - view distance api
-
- // Spigot start
- private final org.bukkit.World.Spigot spigot = new org.bukkit.World.Spigot()
-diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-index 9d2506a042b49089094be79b5d0ed54f088b9625..f2ada37115392466edbed8a2d331084aaaf7b774 100644
---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-@@ -408,6 +408,46 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
- connection.disconnect(message == null ? net.kyori.adventure.text.Component.empty() : message);
- }
- }
-+
-+ @Override
-+ public int getViewDistance() {
-+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ }
-+
-+ @Override
-+ public void setViewDistance(int viewDistance) {
-+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ }
-+
-+ @Override
-+ public int getSimulationDistance() {
-+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ }
-+
-+ @Override
-+ public void setSimulationDistance(int simulationDistance) {
-+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ }
-+
-+ @Override
-+ public int getNoTickViewDistance() {
-+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ }
-+
-+ @Override
-+ public void setNoTickViewDistance(int viewDistance) {
-+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ }
-+
-+ @Override
-+ public int getSendViewDistance() {
-+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ }
-+
-+ @Override
-+ public void setSendViewDistance(int viewDistance) {
-+ throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ }
- // Paper end
-
- @Override
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0137-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch b/patches/removed/1.19.2-legacy-chunksystem/0137-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch
deleted file mode 100644
index 6a3c1b57fe..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0137-Make-targetSize-more-aggressive-in-the-chunk-unload-.patch
+++ /dev/null
@@ -1,36 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Brokkonaut <[email protected]>
-Date: Tue, 7 Feb 2017 16:55:35 -0600
-Subject: [PATCH] Make targetSize more aggressive in the chunk unload queue
-
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 4af8cee31d20e5dcec510439795e7e90fc668128..e086135936e4f6c109cd09a4e4df350702b3510a 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -247,7 +247,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- this.entityMap = new Int2ObjectOpenHashMap();
- this.chunkTypeCache = new Long2ByteOpenHashMap();
- this.chunkSaveCooldowns = new Long2LongOpenHashMap();
-- this.unloadQueue = Queues.newConcurrentLinkedQueue();
-+ this.unloadQueue = new com.destroystokyo.paper.utils.CachedSizeConcurrentLinkedQueue<>(); // Paper - need constant-time size()
- this.structureTemplateManager = structureTemplateManager;
- Path path = session.getDimensionPath(world.dimension());
-
-@@ -674,7 +674,6 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- private void processUnloads(BooleanSupplier shouldKeepTicking) {
- LongIterator longiterator = this.toDrop.iterator();
--
- for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) {
- long j = longiterator.nextLong();
- ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
-@@ -688,7 +687,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
- }
-
-- int k = Math.max(0, this.unloadQueue.size() - 2000);
-+ int k = Math.max(100, this.unloadQueue.size() - 2000); // Paper - Unload more than just up to queue size 2000
-
- Runnable runnable;
-
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0324-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch b/patches/removed/1.19.2-legacy-chunksystem/0324-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch
deleted file mode 100644
index c8325ee866..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0324-Fix-CraftServer-isPrimaryThread-and-MinecraftServer-.patch
+++ /dev/null
@@ -1,43 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 13 May 2019 21:10:59 -0700
-Subject: [PATCH] Fix CraftServer#isPrimaryThread and MinecraftServer
- isMainThread
-
-md_5 changed it so he could shut down the server asynchronously
-from watchdog, although we have patches that prevent that type
-of behavior for this exact reason.
-
-md_5 also placed code in PlayerConnectionUtils that would have
-solved https://bugs.mojang.com/browse/MC-142590, making the change
-to MinecraftServer#isMainThread irrelevant.
-By reverting his change to MinecraftServer#isMainThread packet
-handling that should have been handled synchronously will be handled
-synchronously when the server gets shut down.
-
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 1d554a45097cdf0640788bb796b983f18af31a9f..f7d7e69e29f217c233869951d7d3188816f8216c 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -2327,7 +2327,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- // CraftBukkit start
- @Override
- public boolean isSameThread() {
-- return super.isSameThread() || this.isStopped(); // CraftBukkit - MC-142590
-+ return super.isSameThread() /*|| this.isStopped()*/; // CraftBukkit - MC-142590 // Paper - causes issues elsewhere
- }
-
- public boolean isDebugging() {
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-index fec355dfc7e6353759276f82e6677fd9607e6e7c..2d6f8032cd5a5e7542a4a1cd791852873c93657e 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
-@@ -2075,7 +2075,7 @@ public final class CraftServer implements Server {
-
- @Override
- public boolean isPrimaryThread() {
-- return Thread.currentThread().equals(console.serverThread) || this.console.hasStopped() || !org.spigotmc.AsyncCatcher.enabled; // All bets are off if we have shut down (e.g. due to watchdog)
-+ return Thread.currentThread().equals(console.serverThread); // Paper - Fix issues with detecting main thread properly
- }
-
- // Paper start
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0355-Fix-Light-Command.patch b/patches/removed/1.19.2-legacy-chunksystem/0355-Fix-Light-Command.patch
deleted file mode 100644
index dd54adf3a4..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0355-Fix-Light-Command.patch
+++ /dev/null
@@ -1,186 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Thu, 7 May 2020 19:17:36 -0400
-Subject: [PATCH] Fix Light Command
-
-This lets you run /paper fixlight <chunkRadius> (max 5) to automatically
-fix all light data in the chunks.
-
-diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java
-index 395c43f6440c1e0e47919eef096ea8a8d552ccec..f44ab1d71210e84328661c0feb662989a5635b6d 100644
---- a/src/main/java/io/papermc/paper/command/PaperCommand.java
-+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
-@@ -2,6 +2,7 @@ package io.papermc.paper.command;
-
- import io.papermc.paper.command.subcommands.ChunkDebugCommand;
- import io.papermc.paper.command.subcommands.EntityCommand;
-+import io.papermc.paper.command.subcommands.FixLightCommand;
- import io.papermc.paper.command.subcommands.HeapDumpCommand;
- import io.papermc.paper.command.subcommands.ReloadCommand;
- import io.papermc.paper.command.subcommands.VersionCommand;
-@@ -42,6 +43,7 @@ public final class PaperCommand extends Command {
- commands.put(Set.of("reload"), new ReloadCommand());
- commands.put(Set.of("version"), new VersionCommand());
- commands.put(Set.of("debug", "chunkinfo"), new ChunkDebugCommand());
-+ commands.put(Set.of("fixlight"), new FixLightCommand());
-
- 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/FixLightCommand.java b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..190df802cb24aa360f6cf4d291e38b4b3fe4a2ac
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/command/subcommands/FixLightCommand.java
-@@ -0,0 +1,121 @@
-+package io.papermc.paper.command.subcommands;
-+
-+import io.papermc.paper.command.PaperSubcommand;
-+import java.util.ArrayDeque;
-+import java.util.Deque;
-+import net.minecraft.server.MCUtil;
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.ChunkHolder;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.server.level.ServerPlayer;
-+import net.minecraft.server.level.ThreadedLevelLightEngine;
-+import net.minecraft.world.level.ChunkPos;
-+import net.minecraft.world.level.chunk.LevelChunk;
-+import org.bukkit.command.CommandSender;
-+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;
-+
-+import static net.kyori.adventure.text.Component.text;
-+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
-+import static net.kyori.adventure.text.format.NamedTextColor.RED;
-+
-+@DefaultQualifier(NonNull.class)
-+public final class FixLightCommand implements PaperSubcommand {
-+ @Override
-+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
-+ this.doFixLight(sender, args);
-+ return true;
-+ }
-+
-+ private void doFixLight(final CommandSender sender, final String[] args) {
-+ if (!(sender instanceof Player)) {
-+ sender.sendMessage(text("Only players can use this command", RED));
-+ return;
-+ }
-+ @Nullable Runnable post = null;
-+ int radius = 2;
-+ if (args.length > 0) {
-+ try {
-+ final int parsed = Integer.parseInt(args[0]);
-+ if (parsed < 0) {
-+ sender.sendMessage(text("Radius cannot be negative!", RED));
-+ return;
-+ }
-+ final int maxRadius = 5;
-+ radius = Math.min(maxRadius, parsed);
-+ if (radius != parsed) {
-+ post = () -> sender.sendMessage(text("Radius '" + parsed + "' was not in the required range [0, " + maxRadius + "], it was lowered to the maximum (" + maxRadius + " chunks).", RED));
-+ }
-+ } catch (final Exception e) {
-+ sender.sendMessage(text("'" + args[0] + "' is not a valid number.", RED));
-+ return;
-+ }
-+ }
-+
-+ CraftPlayer player = (CraftPlayer) sender;
-+ ServerPlayer handle = player.getHandle();
-+ ServerLevel world = (ServerLevel) handle.level;
-+ ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine();
-+
-+ net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation());
-+ Deque<ChunkPos> queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius));
-+ updateLight(sender, world, lightengine, queue, post);
-+ }
-+
-+ private void updateLight(
-+ final CommandSender sender,
-+ final ServerLevel world,
-+ final ThreadedLevelLightEngine lightengine,
-+ final Deque<ChunkPos> queue,
-+ final @Nullable Runnable done
-+ ) {
-+ @Nullable ChunkPos coord = queue.poll();
-+ if (coord == null) {
-+ sender.sendMessage(text("All Chunks Light updated", GREEN));
-+ if (done != null) {
-+ done.run();
-+ }
-+ return;
-+ }
-+ world.getChunkSource().getChunkAtAsynchronously(coord.x, coord.z, false, false).whenCompleteAsync((either, ex) -> {
-+ if (ex != null) {
-+ sender.sendMessage(text("Error loading chunk " + coord, RED));
-+ updateLight(sender, world, lightengine, queue, done);
-+ return;
-+ }
-+ @Nullable LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null);
-+ if (chunk == null) {
-+ updateLight(sender, world, lightengine, queue, done);
-+ return;
-+ }
-+ lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize + 16 * 256); // ensure full chunk can fit into queue
-+ sender.sendMessage(text("Updating Light " + coord));
-+ int cx = chunk.getPos().x << 4;
-+ int cz = chunk.getPos().z << 4;
-+ for (int y = 0; y < world.getHeight(); y++) {
-+ for (int x = 0; x < 16; x++) {
-+ for (int z = 0; z < 16; z++) {
-+ net.minecraft.core.BlockPos pos = new net.minecraft.core.BlockPos(cx + x, y, cz + z);
-+ lightengine.checkBlock(pos);
-+ }
-+ }
-+ }
-+ lightengine.tryScheduleUpdate();
-+ @Nullable ChunkHolder visibleChunk = world.getChunkSource().chunkMap.getVisibleChunkIfPresent(chunk.coordinateKey);
-+ if (visibleChunk != null) {
-+ world.getChunkSource().chunkMap.addLightTask(visibleChunk, () -> {
-+ MinecraftServer.getServer().processQueue.add(() -> {
-+ visibleChunk.broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunk.getPos(), lightengine, null, null, true), false);
-+ updateLight(sender, world, lightengine, queue, done);
-+ });
-+ });
-+ } else {
-+ updateLight(sender, world, lightengine, queue, done);
-+ }
-+ lightengine.setTaskPerBatch(world.paperConfig().misc.lightQueueSize);
-+ }, MinecraftServer.getServer());
-+ }
-+}
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 4b24e4d947e96ea0720f8f6bc33470e07c00310d..d60173b03baee4a66da1109795bf6a19737b8bd0 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -141,6 +141,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- private final ChunkTaskPriorityQueueSorter queueSorter;
- private final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> worldgenMailbox;
- public final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mainThreadMailbox;
-+ // Paper start
-+ final ProcessorHandle<ChunkTaskPriorityQueueSorter.Message<Runnable>> mailboxLight;
-+ public void addLightTask(ChunkHolder playerchunk, Runnable run) {
-+ this.mailboxLight.tell(ChunkTaskPriorityQueueSorter.message(playerchunk, run));
-+ }
-+ // Paper end
- public final ChunkProgressListener progressListener;
- private final ChunkStatusUpdateListener chunkStatusListener;
- public final ChunkMap.ChunkDistanceManager distanceManager;
-@@ -284,11 +290,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- this.progressListener = worldGenerationProgressListener;
- this.chunkStatusListener = chunkStatusChangeListener;
-- ProcessorMailbox<Runnable> threadedmailbox1 = ProcessorMailbox.create(executor, "light");
-+ ProcessorMailbox<Runnable> lightthreaded; ProcessorMailbox<Runnable> threadedmailbox1 = lightthreaded = ProcessorMailbox.create(executor, "light"); // Paper
-
- this.queueSorter = new ChunkTaskPriorityQueueSorter(ImmutableList.of(threadedmailbox, mailbox, threadedmailbox1), executor, Integer.MAX_VALUE);
- this.worldgenMailbox = this.queueSorter.getProcessor(threadedmailbox, false);
- this.mainThreadMailbox = this.queueSorter.getProcessor(mailbox, false);
-+ this.mailboxLight = this.queueSorter.getProcessor(lightthreaded, false); // Paper
- this.lightEngine = new ThreadedLevelLightEngine(chunkProvider, this, this.level.dimensionType().hasSkyLight(), threadedmailbox1, this.queueSorter.getProcessor(threadedmailbox1, false));
- this.distanceManager = new ChunkMap.ChunkDistanceManager(executor, mainThreadExecutor);
- this.overworldDataStorage = persistentStateManagerFactory;
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0393-Optimise-ArraySetSorted-removeIf.patch b/patches/removed/1.19.2-legacy-chunksystem/0393-Optimise-ArraySetSorted-removeIf.patch
deleted file mode 100644
index eed4ea8bc2..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0393-Optimise-ArraySetSorted-removeIf.patch
+++ /dev/null
@@ -1,88 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Wed, 15 Apr 2020 18:23:28 -0700
-Subject: [PATCH] Optimise ArraySetSorted#removeIf
-
-Remove iterator allocation and ensure the call is always O(n)
-
-diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index 9309ea89a440606be3e56ef634f5048a72b0009e..1d1ea158d095bb69260929e8d84f2632a875c136 100644
---- a/src/main/java/net/minecraft/server/level/DistanceManager.java
-+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -86,13 +86,27 @@ public abstract class DistanceManager {
- protected void purgeStaleTickets() {
- ++this.ticketTickCounter;
- ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
-+ // Paper start - use optimised removeIf
-+ long[] currChunk = new long[1];
-+ long ticketCounter = DistanceManager.this.ticketTickCounter;
-+ java.util.function.Predicate<Ticket<?>> removeIf = (ticket) -> {
-+ final boolean ret = ticket.timedOut(ticketCounter);
-+ if (ret) {
-+ this.tickingTicketsTracker.removeTicket(currChunk[0], ticket);
-+ }
-+ return ret;
-+ };
-+ // Paper end - use optimised removeIf
-
- while (objectiterator.hasNext()) {
- Entry<SortedArraySet<Ticket<?>>> entry = (Entry) objectiterator.next();
-- Iterator<Ticket<?>> iterator = ((SortedArraySet) entry.getValue()).iterator();
-- boolean flag = false;
-+ // Paper start - use optimised removeIf
-+ Iterator<Ticket<?>> iterator = null;
-+ currChunk[0] = entry.getLongKey();
-+ boolean flag = entry.getValue().removeIf(removeIf);
-
-- while (iterator.hasNext()) {
-+ while (false && iterator.hasNext()) {
-+ // Paper end - use optimised removeIf
- Ticket<?> ticket = (Ticket) iterator.next();
-
- if (ticket.timedOut(this.ticketTickCounter)) {
-diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java
-index d1b2ba24ef54e01c6249c3b2ca16e80f03c001a6..5f1c4c6b9e36f2d6ec43b82cc0e2cae24b800dc4 100644
---- a/src/main/java/net/minecraft/util/SortedArraySet.java
-+++ b/src/main/java/net/minecraft/util/SortedArraySet.java
-@@ -22,6 +22,41 @@ public class SortedArraySet<T> extends AbstractSet<T> {
- this.contents = (T[])castRawArray(new Object[initialCapacity]);
- }
- }
-+ // Paper start - optimise removeIf
-+ @Override
-+ public boolean removeIf(java.util.function.Predicate<? super T> filter) {
-+ // prev. impl used an iterator, which could be n^2 and creates garbage
-+ int i = 0, len = this.size;
-+ T[] backingArray = this.contents;
-+
-+ for (;;) {
-+ if (i >= len) {
-+ return false;
-+ }
-+ if (!filter.test(backingArray[i])) {
-+ ++i;
-+ continue;
-+ }
-+ break;
-+ }
-+
-+ // we only want to write back to backingArray if we really need to
-+
-+ int lastIndex = i; // this is where new elements are shifted to
-+
-+ for (; i < len; ++i) {
-+ T curr = backingArray[i];
-+ if (!filter.test(curr)) { // if test throws we're screwed
-+ backingArray[lastIndex++] = curr;
-+ }
-+ }
-+
-+ // cleanup end
-+ Arrays.fill(backingArray, lastIndex, len, null);
-+ this.size = lastIndex;
-+ return true;
-+ }
-+ // Paper end - optimise removeIf
-
- public static <T extends Comparable<T>> SortedArraySet<T> create() {
- return create(10);
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0397-Fix-Chunk-Post-Processing-deadlock-risk.patch b/patches/removed/1.19.2-legacy-chunksystem/0397-Fix-Chunk-Post-Processing-deadlock-risk.patch
deleted file mode 100644
index 13219b5e56..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0397-Fix-Chunk-Post-Processing-deadlock-risk.patch
+++ /dev/null
@@ -1,73 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 18 Apr 2020 04:36:11 -0400
-Subject: [PATCH] Fix Chunk Post Processing deadlock risk
-
-See: https://gist.github.com/aikar/dd22bbd2a3d78a2fd3d92e95e9f28dc6
-
-as part of post processing a chunk, we can call ChunkConverter.
-
-ChunkConverter then kicks off major physics updates, and when blocks
-that have connections across chunk boundaries occur, a recursive risk
-can occur where A updates a block that triggers a physics request.
-
-That physics request may trigger a chunk request, that then enqueues
-a task into the Mailbox ChunkTaskQueueSorter.
-
-If anything requests that same chunk that is in the middle of conversion,
-it's mailbox queue is going to be held up, so the subsequent chunk request
-will be unable to proceed.
-
-We delay post processing of Chunk.A() 1 "pass" by re stuffing it back into
-the executor so that the mailbox ChunkQueue is now considered empty.
-
-This successfully fixed a reoccurring and highly reproducible crash
-for heightmaps.
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index fcc9dd6e1c54e4ca16102150aa4c12ecc7de06df..aed3da6ef2d498d3f1c9c64177bf1ba6b8157493 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -193,6 +193,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- };
- // CraftBukkit end
-
-+ final CallbackExecutor chunkLoadConversionCallbackExecutor = new CallbackExecutor(); // Paper
- // Paper start - distance maps
- private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
-
-@@ -1132,16 +1133,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- });
- CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completablefuture1 = completablefuture.thenApplyAsync((either) -> {
- return either.mapLeft((list) -> {
-- return (LevelChunk) list.get(list.size() / 2);
-- });
-- }, (runnable) -> {
-- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
-- }).thenApplyAsync((either) -> {
-- return either.ifLeft((chunk) -> {
-+ // Paper start - revert 1.18.2 diff
-+ final LevelChunk chunk = (LevelChunk) list.get(list.size() / 2);
- chunk.postProcessGeneration();
- this.level.startTickingChunk(chunk);
-+ return chunk;
- });
-- }, this.mainThreadExecutor);
-+ }, (runnable) -> {
-+ this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, () -> ChunkMap.this.chunkLoadConversionCallbackExecutor.execute(runnable))); // Paper - delay running Chunk post processing until outside of the sorter to prevent a deadlock scenario when post processing causes another chunk request.
-+ }); // Paper end - revert 1.18.2 diff
-
- completablefuture1.thenAcceptAsync((either) -> {
- either.ifLeft((chunk) -> {
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 471cc00c677b6581ba84c8cac25d2246c2a14bc9..497827822a64eeff2a4901f0e7c62f0f2c359b48 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -994,6 +994,7 @@ public class ServerChunkCache extends ChunkSource {
- return super.pollTask() || execChunkTask; // Paper
- }
- } finally {
-+ chunkMap.chunkLoadConversionCallbackExecutor.run(); // Paper - Add chunk load conversion callback executor to prevent deadlock due to recursion in the chunk task queue sorter
- chunkMap.callbackExecutor.run();
- }
- // CraftBukkit end
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0429-Optimize-ServerLevels-chunk-level-checking-methods.patch b/patches/removed/1.19.2-legacy-chunksystem/0429-Optimize-ServerLevels-chunk-level-checking-methods.patch
deleted file mode 100644
index 2bcf0781f9..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0429-Optimize-ServerLevels-chunk-level-checking-methods.patch
+++ /dev/null
@@ -1,69 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Thu, 16 Apr 2020 16:13:59 -0700
-Subject: [PATCH] Optimize ServerLevels chunk level checking methods
-
-These can be hot functions (i.e entity ticking and block ticking),
-so inline where possible, and avoid the abstraction of the
-Either class.
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index a778b4b5b2413c25c2f0f0efc72ba1d362d89acf..c9ecc7593c299b351308634db44596a76fd0c09b 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -2255,19 +2255,22 @@ public class ServerLevel extends Level implements WorldGenLevel {
- }
-
- private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) {
-- return this.areEntitiesLoaded(chunkPos) && this.chunkSource.isPositionTicking(chunkPos);
-+ // Paper start - optimize is ticking ready type functions
-+ ChunkHolder chunkHolder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos);
-+ return chunkHolder != null && this.chunkSource.isPositionTicking(chunkPos) && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos);
-+ // Paper end
- }
-
- public boolean isPositionEntityTicking(BlockPos pos) {
-- return this.entityManager.canPositionTick(pos) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos));
-+ return this.entityManager.canPositionTick(ChunkPos.asLong(pos)) && this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(ChunkPos.asLong(pos)); // Paper
- }
-
- public boolean isNaturalSpawningAllowed(BlockPos pos) {
-- return this.entityManager.canPositionTick(pos);
-+ return this.entityManager.canPositionTick(ChunkPos.asLong(pos)); // Paper
- }
-
- public boolean isNaturalSpawningAllowed(ChunkPos pos) {
-- return this.entityManager.canPositionTick(pos);
-+ return this.entityManager.canPositionTick(pos.toLong()); // Paper
- }
-
- private final class EntityCallbacks implements LevelCallback<Entity> {
-diff --git a/src/main/java/net/minecraft/world/level/ChunkPos.java b/src/main/java/net/minecraft/world/level/ChunkPos.java
-index 2d41f619577b41d6420159668bbab70fb6c762eb..ed0b136e99def41d4377f2004477826b3546a145 100644
---- a/src/main/java/net/minecraft/world/level/ChunkPos.java
-+++ b/src/main/java/net/minecraft/world/level/ChunkPos.java
-@@ -60,7 +60,7 @@ public class ChunkPos {
- }
-
- public static long asLong(BlockPos pos) {
-- return asLong(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()));
-+ return (((long)pos.getX() >> 4) & 4294967295L) | ((((long)pos.getZ() >> 4) & 4294967295L) << 32); // Paper - inline
- }
-
- public static int getX(long pos) {
-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 16519a6414f6f6418de40b714555a52631980617..a5dc8e715c86c1e70a9cf3d99c9cd457a6666b70 100644
---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
-+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
-@@ -395,6 +395,11 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
- public LevelEntityGetter<T> getEntityGetter() {
- return this.entityGetter;
- }
-+ // Paper start
-+ public final boolean canPositionTick(long position) {
-+ return this.chunkVisibility.get(position).isTicking();
-+ }
-+ // Paper end
-
- public boolean canPositionTick(BlockPos pos) {
- return ((Visibility) this.chunkVisibility.get(ChunkPos.asLong(pos))).isTicking();
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0482-Improve-Chunk-Status-Transition-Speed.patch b/patches/removed/1.19.2-legacy-chunksystem/0482-Improve-Chunk-Status-Transition-Speed.patch
deleted file mode 100644
index 352e70742e..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0482-Improve-Chunk-Status-Transition-Speed.patch
+++ /dev/null
@@ -1,81 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Fri, 29 May 2020 23:32:14 -0400
-Subject: [PATCH] Improve Chunk Status Transition Speed
-
-When a chunk is loaded from disk that has already been generated,
-the server has to promote the chunk through the system to reach
-it's current desired status level.
-
-This results in every single status transition going from the main thread
-to the world gen threads, only to discover it has no work it actually
-needs to do.... and then it returns back to main.
-
-This back and forth costs a lot of time and can really delay chunk loads
-when the server is under high TPS due to their being a lot of time in
-between chunk load times, as well as hogs up the chunk threads from doing
-actual generation and light work.
-
-Additionally, the whole task system uses a lot of CPU on the server threads anyways.
-
-So by optimizing status transitions for status's that are already complete,
-we can run them to the desired level while on main thread (where it has
-to happen anyways) instead of ever jumping to world gen thread.
-
-This will improve chunk loading effeciency to be reduced down to the following
-scenario / path:
-
-1) MAIN: Chunk Requested, Load Request sent to ChunkTaskManager / IO Queue
-2) IO: Once position in queue comes, submit read IO data and schedule to chunk task thread
-3) CHUNK: Once IO is loaded and position in queue comes, deserialize the chunk data, process conversions, submit to main queue
-4) MAIN: next Chunk Task process (Mid Tick or End Of Tick), load chunk data into world (POI, main thread tasks)
-5) MAIN: process status transitions all the way to LIGHT, light schedules Threaded task
-6) SERVER: Light tasks register light enablement for chunk and any lighting needing to be done
-7) MAIN: Task returns to main, finish processing to FULL/TICKING status
-
-Previously would have hopped to SERVER around 12+ times there extra.
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index ac42029596ae0c824bf33a4058ac1009740e29ea..a699107b1afea1c52e5a7e93af8f39ae9e1955b3 100644
---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -101,6 +101,13 @@ public class ChunkHolder {
- // Paper end - optimise anyPlayerCloseEnoughForSpawning
- long lastAutoSaveTime; // Paper - incremental autosave
- long inactiveTimeStart; // Paper - incremental autosave
-+ // Paper start - optimize chunk status progression without jumping through thread pool
-+ public boolean canAdvanceStatus() {
-+ ChunkStatus status = getChunkHolderStatus();
-+ ChunkAccess chunk = getAvailableChunkNow();
-+ return chunk != null && (status == null || chunk.getStatus().isOrAfter(getNextStatus(status)));
-+ }
-+ // Paper end
-
- public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
- this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 7493da0f1c3f8ab0ebc517347ef23fbe2747a306..5a78ee69748b2b7b57a9adcff0a4718b1cc0c4ea 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -726,7 +726,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- return either.mapLeft((list) -> {
- return (LevelChunk) list.get(list.size() / 2);
- });
-- }, this.mainThreadExecutor);
-+ }, this.mainInvokingExecutor); // Paper
- }
-
- @Nullable
-@@ -1144,6 +1144,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- return "chunkGenerate " + requiredStatus.getName();
- });
- Executor executor = (runnable) -> {
-+ // Paper start - optimize chunk status progression without jumping through thread pool
-+ if (holder.canAdvanceStatus()) {
-+ this.mainInvokingExecutor.execute(runnable);
-+ return;
-+ }
-+ // Paper end
- this.worldgenMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
- };
-
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0720-Do-not-allow-the-server-to-unload-chunks-at-request-.patch b/patches/removed/1.19.2-legacy-chunksystem/0720-Do-not-allow-the-server-to-unload-chunks-at-request-.patch
deleted file mode 100644
index ce98eec585..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0720-Do-not-allow-the-server-to-unload-chunks-at-request-.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Thu, 11 Mar 2021 02:32:30 -0800
-Subject: [PATCH] Do not allow the server to unload chunks at request of
- plugins
-
-In general the chunk system is not well suited for this behavior,
-especially if it is called during a chunk load. The chunks pushed
-to be unloaded will simply be unloaded next tick, rather than
-immediately.
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index b5f46703e536f8138ff4e6769485c45b35941f9f..f3ab1691948c46477888776d28791ce24e7aa93d 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -696,6 +696,7 @@ public class ServerChunkCache extends ChunkSource {
-
- // CraftBukkit start - modelled on below
- public void purgeUnload() {
-+ if (true) return; // Paper - tickets will be removed later, this behavior isn't really well accounted for by the chunk system
- this.level.getProfiler().push("purge");
- this.distanceManager.purgeStaleTickets();
- this.runDistanceManagerUpdates();
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0722-Correctly-handle-recursion-for-chunkholder-updates.patch b/patches/removed/1.19.2-legacy-chunksystem/0722-Correctly-handle-recursion-for-chunkholder-updates.patch
deleted file mode 100644
index 17b34c16ac..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0722-Correctly-handle-recursion-for-chunkholder-updates.patch
+++ /dev/null
@@ -1,37 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sun, 21 Mar 2021 17:32:47 -0700
-Subject: [PATCH] Correctly handle recursion for chunkholder updates
-
-If a chunk ticket level is brought up while unloading it would
-cause a recursive call which would handle the increase but then
-the caller would think the chunk would be unloaded.
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index c4046b364d1896b781e23c92b241ec73c239d3a0..9c0bf31c3c362632241c95338a3f8d67bbd4fdc5 100644
---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -472,8 +472,10 @@ public class ChunkHolder {
- playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state);
- }
-
-+ protected long updateCount; // Paper - correctly handle recursion
- protected void updateFutures(ChunkMap chunkStorage, Executor executor) {
- io.papermc.paper.util.TickThread.ensureTickThread("Async ticket level update"); // Paper
-+ long updateCount = ++this.updateCount; // Paper - correctly handle recursion
- ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel);
- ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel);
- boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE;
-@@ -515,6 +517,12 @@ public class ChunkHolder {
-
- // Run callback right away if the future was already done
- chunkStorage.callbackExecutor.run();
-+ // Paper start - correctly handle recursion
-+ if (this.updateCount != updateCount) {
-+ // something else updated ticket level for us.
-+ return;
-+ }
-+ // Paper end - correctly handle recursion
- }
- // CraftBukkit end
-
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0724-Fix-chunks-refusing-to-unload-at-low-TPS.patch b/patches/removed/1.19.2-legacy-chunksystem/0724-Fix-chunks-refusing-to-unload-at-low-TPS.patch
deleted file mode 100644
index 27155400d9..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0724-Fix-chunks-refusing-to-unload-at-low-TPS.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 1 Feb 2021 15:35:14 -0800
-Subject: [PATCH] Fix chunks refusing to unload at low TPS
-
-The full chunk future is appended to the chunk save future, but
-when moving to unloaded ticket level it is not being completed with
-the empty chunk access, so the chunk save must wait for the full
-chunk future to complete. We can simply schedule to the immediate
-executor to get this effect, rather than the main mailbox.
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 5a78ee69748b2b7b57a9adcff0a4718b1cc0c4ea..a3fceb2608b3be80941dfe2570999b270429e0c6 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -1352,9 +1352,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- return chunk;
- });
-- }, (runnable) -> {
-- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
-- });
-+ }, this.mainThreadExecutor); // Paper - queue to execute immediately so this doesn't delay chunk unloading
- }
-
- public int getTickingGenerated() {
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0725-Do-not-allow-ticket-level-changes-while-unloading-pl.patch b/patches/removed/1.19.2-legacy-chunksystem/0725-Do-not-allow-ticket-level-changes-while-unloading-pl.patch
deleted file mode 100644
index 302867667a..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0725-Do-not-allow-ticket-level-changes-while-unloading-pl.patch
+++ /dev/null
@@ -1,62 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sat, 19 Sep 2020 15:29:16 -0700
-Subject: [PATCH] Do not allow ticket level changes while unloading
- playerchunks
-
-Sync loading the chunk at this stage would cause it to load
-older data, as well as screwing our region state.
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index a3fceb2608b3be80941dfe2570999b270429e0c6..b34c90497a5492c289839ba74df9f2f27e29370e 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -316,6 +316,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
- // Paper end
-
-+ boolean unloadingPlayerChunk = false; // Paper - do not allow ticket level changes while unloading chunks
- public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop<Runnable> mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier<DimensionDataStorage> persistentStateManagerFactory, int viewDistance, boolean dsync) {
- super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
- // Paper - don't copy
-@@ -731,6 +732,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- @Nullable
- ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) {
-+ if (this.unloadingPlayerChunk) { net.minecraft.server.MinecraftServer.LOGGER.error("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper
- if (k > ChunkMap.MAX_CHUNK_DISTANCE && level > ChunkMap.MAX_CHUNK_DISTANCE) {
- return holder;
- } else {
-@@ -945,6 +947,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- if (completablefuture1 != completablefuture) {
- this.scheduleUnload(pos, holder);
- } else {
-+ // Paper start - do not allow ticket level changes while unloading chunks
-+ org.spigotmc.AsyncCatcher.catchOp("playerchunk unload");
-+ boolean unloadingBefore = this.unloadingPlayerChunk;
-+ this.unloadingPlayerChunk = true;
-+ try {
-+ // Paper end - do not allow ticket level changes while unloading chunks
- // Paper start
- boolean removed;
- if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) {
-@@ -978,6 +986,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- } else if (removed) { // Paper start
- net.minecraft.server.ChunkSystem.onChunkHolderDelete(this.level, holder);
- } // Paper end
-+ } finally { this.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes while unloading chunks
-
- }
- };
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index f3ab1691948c46477888776d28791ce24e7aa93d..29ba8971ceffbac68290f6063a69c98065e9bcba 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -632,6 +632,7 @@ public class ServerChunkCache extends ChunkSource {
-
- public boolean runDistanceManagerUpdates() {
- if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority
-+ if (this.chunkMap.unloadingPlayerChunk) { LOGGER.error("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper
- boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
- boolean flag1 = this.chunkMap.promoteChunkMap();
-
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0726-Do-not-allow-ticket-level-changes-when-updating-chun.patch b/patches/removed/1.19.2-legacy-chunksystem/0726-Do-not-allow-ticket-level-changes-when-updating-chun.patch
deleted file mode 100644
index d3e3677642..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0726-Do-not-allow-ticket-level-changes-when-updating-chun.patch
+++ /dev/null
@@ -1,40 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sun, 20 Jun 2021 00:08:13 -0700
-Subject: [PATCH] Do not allow ticket level changes when updating chunk ticking
- state
-
-This WILL cause state corruption if it happens. So, don't
-allow it.
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 9c0bf31c3c362632241c95338a3f8d67bbd4fdc5..a2b5f6457b08e4e02544dc71fbf383b5a67a2d69 100644
---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -452,7 +452,13 @@ public class ChunkHolder {
- CompletableFuture<Void> completablefuture1 = new CompletableFuture();
-
- completablefuture1.thenRunAsync(() -> {
-+ // Paper start - do not allow ticket level changes
-+ boolean unloadingBefore = this.chunkMap.unloadingPlayerChunk;
-+ this.chunkMap.unloadingPlayerChunk = true;
-+ try {
-+ // Paper end - do not allow ticket level changes
- playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state);
-+ } finally { this.chunkMap.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes
- }, executor);
- this.pendingFullStateConfirmation = completablefuture1;
- completablefuture.thenAccept((either) -> {
-@@ -469,7 +475,12 @@ public class ChunkHolder {
-
- private void demoteFullChunk(ChunkMap playerchunkmap, ChunkHolder.FullChunkStatus playerchunk_state) {
- this.pendingFullStateConfirmation.cancel(false);
-+ // Paper start - do not allow ticket level changes
-+ boolean unloadingBefore = this.chunkMap.unloadingPlayerChunk;
-+ this.chunkMap.unloadingPlayerChunk = true;
-+ try { // Paper end - do not allow ticket level changes
- playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state);
-+ } finally { this.chunkMap.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes
- }
-
- protected long updateCount; // Paper - correctly handle recursion
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0729-Prevent-unload-calls-removing-tickets-for-sync-loads.patch b/patches/removed/1.19.2-legacy-chunksystem/0729-Prevent-unload-calls-removing-tickets-for-sync-loads.patch
deleted file mode 100644
index 516b5b8e0c..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0729-Prevent-unload-calls-removing-tickets-for-sync-loads.patch
+++ /dev/null
@@ -1,80 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Thu, 18 Jun 2020 18:23:20 -0700
-Subject: [PATCH] Prevent unload() calls removing tickets for sync loads
-
-
-diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index b2df5e18ce5260a9781052db7afb0b9786fb887c..537d34a0325a985948c744929b90144a66a35ee3 100644
---- a/src/main/java/net/minecraft/server/level/DistanceManager.java
-+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -545,7 +545,7 @@ public abstract class DistanceManager {
- }
-
- public void removeTicketsOnClosing() {
-- ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD); // Paper - add additional tickets to preserve
-+ ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD); // Paper - add additional tickets to preserve
- ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
-
- while (objectiterator.hasNext()) {
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 29ba8971ceffbac68290f6063a69c98065e9bcba..2390fcbc1b21653b1753a58da33f936cec43d0cb 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -537,6 +537,8 @@ public class ServerChunkCache extends ChunkSource {
- return completablefuture;
- }
-
-+ private long syncLoadCounter; // Paper - prevent plugin unloads from removing our ticket
-+
- private CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getChunkFutureMainThread(int chunkX, int chunkZ, ChunkStatus leastStatus, boolean create) {
- // Paper start - add isUrgent - old sig left in place for dirty nms plugins
- return getChunkFutureMainThread(chunkX, chunkZ, leastStatus, create, false);
-@@ -555,9 +557,12 @@ public class ServerChunkCache extends ChunkSource {
- ChunkHolder.FullChunkStatus currentChunkState = ChunkHolder.getFullChunkStatus(playerchunk.getTicketLevel());
- currentlyUnloading = (oldChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !currentChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER));
- }
-+ final Long identifier; // Paper - prevent plugin unloads from removing our ticket
- if (create && !currentlyUnloading) {
- // CraftBukkit end
- this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair);
-+ identifier = Long.valueOf(this.syncLoadCounter++); // Paper - prevent plugin unloads from removing our ticket
-+ this.distanceManager.addTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Paper - prevent plugin unloads from removing our ticket
- if (isUrgent) this.distanceManager.markUrgent(chunkcoordintpair); // Paper - Chunk priority
- if (this.chunkAbsent(playerchunk, l)) {
- ProfilerFiller gameprofilerfiller = this.level.getProfiler();
-@@ -568,13 +573,21 @@ public class ServerChunkCache extends ChunkSource {
- playerchunk = this.getVisibleChunkIfPresent(k);
- gameprofilerfiller.pop();
- if (this.chunkAbsent(playerchunk, l)) {
-+ this.distanceManager.removeTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Paper
- throw (IllegalStateException) Util.pauseInIde(new IllegalStateException("No chunk holder after ticket has been added"));
- }
- }
-- }
-
-+ } else { identifier = null; } // Paper - prevent plugin unloads from removing our ticket
- // Paper start - Chunk priority
- CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> future = this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(leastStatus, this.chunkMap);
-+ // Paper start - prevent plugin unloads from removing our ticket
-+ if (create && !currentlyUnloading) {
-+ future.thenAcceptAsync((either) -> {
-+ ServerChunkCache.this.distanceManager.removeTicket(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier);
-+ }, ServerChunkCache.this.mainThreadProcessor);
-+ }
-+ // Paper end - prevent plugin unloads from removing our ticket
- if (isUrgent) {
- future.thenAccept(either -> this.distanceManager.clearUrgent(chunkcoordintpair));
- }
-diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java
-index 3c1698ba0d3bc412ab957777d9b5211dbc555208..41ddcf6775f99c56cf4b13b284420061e5dd6bdc 100644
---- a/src/main/java/net/minecraft/server/level/TicketType.java
-+++ b/src/main/java/net/minecraft/server/level/TicketType.java
-@@ -31,6 +31,7 @@ public class TicketType<T> {
- public static final TicketType<Unit> PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit
- public static final TicketType<org.bukkit.plugin.Plugin> PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit
- public static final TicketType<Long> DELAY_UNLOAD = create("delay_unload", Long::compareTo, 300); // Paper
-+ public static final TicketType<Long> REQUIRED_LOAD = create("required_load", Long::compareTo); // Paper - make sure getChunkAt does not fail
-
- public static <T> TicketType<T> create(String name, Comparator<T> argumentComparator) {
- return new TicketType<>(name, argumentComparator, 0L);
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0734-Rewrite-entity-bounding-box-lookup-calls.patch b/patches/removed/1.19.2-legacy-chunksystem/0734-Rewrite-entity-bounding-box-lookup-calls.patch
deleted file mode 100644
index 5179560e78..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0734-Rewrite-entity-bounding-box-lookup-calls.patch
+++ /dev/null
@@ -1,1300 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Thu, 17 Jun 2021 19:55:02 -0700
-Subject: [PATCH] Rewrite entity bounding box lookup calls
-
-For whatever reason, Mojang thought it was OK to make this system
-scale logn relative to the number of entity sections loaded.
-On top of that, they do a hashtable lookup per section - before
-this was just a basic array access.
-
-This patch brings back entity slices for lookup only.
-
-diff --git a/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..47b5f75d9f27cf3ab947fd1f69cbd609fb9f2749
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/world/ChunkEntitySlices.java
-@@ -0,0 +1,500 @@
-+package io.papermc.paper.world;
-+
-+import com.destroystokyo.paper.util.maplist.EntityList;
-+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
-+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
-+import net.minecraft.server.level.ChunkHolder;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.util.Mth;
-+import net.minecraft.world.entity.Entity;
-+import net.minecraft.world.entity.EntityType;
-+import net.minecraft.world.entity.boss.EnderDragonPart;
-+import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
-+import net.minecraft.world.phys.AABB;
-+import java.util.Arrays;
-+import java.util.Iterator;
-+import java.util.List;
-+import java.util.function.Predicate;
-+
-+public final class ChunkEntitySlices {
-+
-+ protected final int minSection;
-+ protected final int maxSection;
-+ protected final int chunkX;
-+ protected final int chunkZ;
-+ protected final ServerLevel world;
-+
-+ protected final EntityCollectionBySection allEntities;
-+ protected final EntityCollectionBySection hardCollidingEntities;
-+ protected final Reference2ObjectOpenHashMap<Class<? extends Entity>, EntityCollectionBySection> entitiesByClass;
-+ protected final EntityList entities = new EntityList();
-+
-+ public ChunkHolder.FullChunkStatus status;
-+
-+ // TODO implement container search optimisations
-+
-+ public ChunkEntitySlices(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkHolder.FullChunkStatus status,
-+ final int minSection, final int maxSection) { // inclusive, inclusive
-+ this.minSection = minSection;
-+ this.maxSection = maxSection;
-+ this.chunkX = chunkX;
-+ this.chunkZ = chunkZ;
-+ this.world = world;
-+
-+ this.allEntities = new EntityCollectionBySection(this);
-+ this.hardCollidingEntities = new EntityCollectionBySection(this);
-+ this.entitiesByClass = new Reference2ObjectOpenHashMap<>();
-+
-+ this.status = status;
-+ }
-+
-+ // Paper start - optimise CraftChunk#getEntities
-+ public org.bukkit.entity.Entity[] getChunkEntities() {
-+ List<org.bukkit.entity.Entity> ret = new java.util.ArrayList<>();
-+ final Entity[] entities = this.entities.getRawData();
-+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) {
-+ final Entity entity = entities[i];
-+ if (entity == null) {
-+ continue;
-+ }
-+ final org.bukkit.entity.Entity bukkit = entity.getBukkitEntity();
-+ if (bukkit != null && bukkit.isValid()) {
-+ ret.add(bukkit);
-+ }
-+ }
-+
-+ return ret.toArray(new org.bukkit.entity.Entity[0]);
-+ }
-+ // Paper end - optimise CraftChunk#getEntities
-+
-+ public boolean isEmpty() {
-+ return this.entities.size() == 0;
-+ }
-+
-+ private void updateTicketLevels() {
-+ final Entity[] entities = this.entities.getRawData();
-+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) {
-+ final Entity entity = entities[i];
-+ entity.chunkStatus = this.status;
-+ }
-+ }
-+
-+ public synchronized void updateStatus(final ChunkHolder.FullChunkStatus status) {
-+ this.status = status;
-+ this.updateTicketLevels();
-+ }
-+
-+ public synchronized void addEntity(final Entity entity, final int chunkSection) {
-+ if (!this.entities.add(entity)) {
-+ return;
-+ }
-+ entity.chunkStatus = this.status;
-+ final int sectionIndex = chunkSection - this.minSection;
-+
-+ this.allEntities.addEntity(entity, sectionIndex);
-+
-+ if (entity.hardCollides()) {
-+ this.hardCollidingEntities.addEntity(entity, sectionIndex);
-+ }
-+
-+ for (final Iterator<Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection>> iterator =
-+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
-+ final Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection> entry = iterator.next();
-+
-+ if (entry.getKey().isInstance(entity)) {
-+ entry.getValue().addEntity(entity, sectionIndex);
-+ }
-+ }
-+ }
-+
-+ public synchronized void removeEntity(final Entity entity, final int chunkSection) {
-+ if (!this.entities.remove(entity)) {
-+ return;
-+ }
-+ entity.chunkStatus = ChunkHolder.FullChunkStatus.INACCESSIBLE;
-+ final int sectionIndex = chunkSection - this.minSection;
-+
-+ this.allEntities.removeEntity(entity, sectionIndex);
-+
-+ if (entity.hardCollides()) {
-+ this.hardCollidingEntities.removeEntity(entity, sectionIndex);
-+ }
-+
-+ for (final Iterator<Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection>> iterator =
-+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) {
-+ final Reference2ObjectMap.Entry<Class<? extends Entity>, EntityCollectionBySection> entry = iterator.next();
-+
-+ if (entry.getKey().isInstance(entity)) {
-+ entry.getValue().removeEntity(entity, sectionIndex);
-+ }
-+ }
-+ }
-+
-+ public void getHardCollidingEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
-+ this.hardCollidingEntities.getEntities(except, box, into, predicate);
-+ }
-+
-+ public void getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
-+ this.allEntities.getEntitiesWithEnderDragonParts(except, box, into, predicate);
-+ }
-+
-+ public <T extends Entity> void getEntities(final EntityType<?> type, final AABB box, final List<? super T> into,
-+ final Predicate<? super T> predicate) {
-+ this.allEntities.getEntities(type, box, (List)into, (Predicate)predicate);
-+ }
-+
-+ protected EntityCollectionBySection initClass(final Class<? extends Entity> clazz) {
-+ final EntityCollectionBySection ret = new EntityCollectionBySection(this);
-+
-+ for (int sectionIndex = 0; sectionIndex < this.allEntities.entitiesBySection.length; ++sectionIndex) {
-+ final BasicEntityList<Entity> sectionEntities = this.allEntities.entitiesBySection[sectionIndex];
-+ if (sectionEntities == null) {
-+ continue;
-+ }
-+
-+ final Entity[] storage = sectionEntities.storage;
-+
-+ for (int i = 0, len = Math.min(storage.length, sectionEntities.size()); i < len; ++i) {
-+ final Entity entity = storage[i];
-+
-+ if (clazz.isInstance(entity)) {
-+ ret.addEntity(entity, sectionIndex);
-+ }
-+ }
-+ }
-+
-+ return ret;
-+ }
-+
-+ public <T extends Entity> void getEntities(final Class<? extends T> clazz, final Entity except, final AABB box, final List<? super T> into,
-+ final Predicate<? super T> predicate) {
-+ EntityCollectionBySection collection = this.entitiesByClass.get(clazz);
-+ if (collection != null) {
-+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate);
-+ } else {
-+ synchronized (this) {
-+ this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz));
-+ }
-+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate);
-+ }
-+ }
-+
-+ public synchronized void updateEntity(final Entity entity) {
-+ /*// TODO
-+ if (prev aabb != entity.getBoundingBox()) {
-+ this.entityMap.delete(entity, prev aabb);
-+ this.entityMap.insert(entity, prev aabb = entity.getBoundingBox());
-+ }*/
-+ }
-+
-+ protected static final class BasicEntityList<E extends Entity> {
-+
-+ protected static final Entity[] EMPTY = new Entity[0];
-+ protected static final int DEFAULT_CAPACITY = 4;
-+
-+ protected E[] storage;
-+ protected int size;
-+
-+ public BasicEntityList() {
-+ this(0);
-+ }
-+
-+ public BasicEntityList(final int cap) {
-+ this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]);
-+ }
-+
-+ public boolean isEmpty() {
-+ return this.size == 0;
-+ }
-+
-+ public int size() {
-+ return this.size;
-+ }
-+
-+ private void resize() {
-+ if (this.storage == EMPTY) {
-+ this.storage = (E[])new Entity[DEFAULT_CAPACITY];
-+ } else {
-+ this.storage = Arrays.copyOf(this.storage, this.storage.length * 2);
-+ }
-+ }
-+
-+ public void add(final E entity) {
-+ final int idx = this.size++;
-+ if (idx >= this.storage.length) {
-+ this.resize();
-+ this.storage[idx] = entity;
-+ } else {
-+ this.storage[idx] = entity;
-+ }
-+ }
-+
-+ public int indexOf(final E entity) {
-+ final E[] storage = this.storage;
-+
-+ for (int i = 0, len = Math.min(this.storage.length, this.size); i < len; ++i) {
-+ if (storage[i] == entity) {
-+ return i;
-+ }
-+ }
-+
-+ return -1;
-+ }
-+
-+ public boolean remove(final E entity) {
-+ final int idx = this.indexOf(entity);
-+ if (idx == -1) {
-+ return false;
-+ }
-+
-+ final int size = --this.size;
-+ final E[] storage = this.storage;
-+ if (idx != size) {
-+ System.arraycopy(storage, idx + 1, storage, idx, size - idx);
-+ }
-+
-+ storage[size] = null;
-+
-+ return true;
-+ }
-+
-+ public boolean has(final E entity) {
-+ return this.indexOf(entity) != -1;
-+ }
-+ }
-+
-+ protected static final class EntityCollectionBySection {
-+
-+ protected final ChunkEntitySlices manager;
-+ protected final long[] nonEmptyBitset;
-+ protected final BasicEntityList<Entity>[] entitiesBySection;
-+ protected int count;
-+
-+ public EntityCollectionBySection(final ChunkEntitySlices manager) {
-+ this.manager = manager;
-+
-+ final int sectionCount = manager.maxSection - manager.minSection + 1;
-+
-+ this.nonEmptyBitset = new long[(sectionCount + (Long.SIZE - 1)) >>> 6]; // (sectionCount + (Long.SIZE - 1)) / Long.SIZE
-+ this.entitiesBySection = new BasicEntityList[sectionCount];
-+ }
-+
-+ public void addEntity(final Entity entity, final int sectionIndex) {
-+ BasicEntityList<Entity> list = this.entitiesBySection[sectionIndex];
-+
-+ if (list != null && list.has(entity)) {
-+ return;
-+ }
-+
-+ if (list == null) {
-+ this.entitiesBySection[sectionIndex] = list = new BasicEntityList<>();
-+ this.nonEmptyBitset[sectionIndex >>> 6] |= (1L << (sectionIndex & (Long.SIZE - 1)));
-+ }
-+
-+ list.add(entity);
-+ ++this.count;
-+ }
-+
-+ public void removeEntity(final Entity entity, final int sectionIndex) {
-+ final BasicEntityList<Entity> list = this.entitiesBySection[sectionIndex];
-+
-+ if (list == null || !list.remove(entity)) {
-+ return;
-+ }
-+
-+ --this.count;
-+
-+ if (list.isEmpty()) {
-+ this.entitiesBySection[sectionIndex] = null;
-+ this.nonEmptyBitset[sectionIndex >>> 6] ^= (1L << (sectionIndex & (Long.SIZE - 1)));
-+ }
-+ }
-+
-+ public void getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
-+ if (this.count == 0) {
-+ return;
-+ }
-+
-+ final int minSection = this.manager.minSection;
-+ final int maxSection = this.manager.maxSection;
-+
-+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
-+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
-+
-+ // TODO use the bitset
-+
-+ final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
-+
-+ for (int section = min; section <= max; ++section) {
-+ final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
-+
-+ if (list == null) {
-+ continue;
-+ }
-+
-+ final Entity[] storage = list.storage;
-+
-+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
-+ final Entity entity = storage[i];
-+
-+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) {
-+ continue;
-+ }
-+
-+ if (predicate != null && !predicate.test(entity)) {
-+ continue;
-+ }
-+
-+ into.add(entity);
-+ }
-+ }
-+ }
-+
-+ public void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List<Entity> into,
-+ final Predicate<? super Entity> predicate) {
-+ if (this.count == 0) {
-+ return;
-+ }
-+
-+ final int minSection = this.manager.minSection;
-+ final int maxSection = this.manager.maxSection;
-+
-+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
-+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
-+
-+ // TODO use the bitset
-+
-+ final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
-+
-+ for (int section = min; section <= max; ++section) {
-+ final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
-+
-+ if (list == null) {
-+ continue;
-+ }
-+
-+ final Entity[] storage = list.storage;
-+
-+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
-+ final Entity entity = storage[i];
-+
-+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) {
-+ continue;
-+ }
-+
-+ if (predicate == null || predicate.test(entity)) {
-+ into.add(entity);
-+ } // else: continue to test the ender dragon parts
-+
-+ if (entity instanceof EnderDragon) {
-+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) {
-+ if (part == except || !part.getBoundingBox().intersects(box)) {
-+ continue;
-+ }
-+
-+ if (predicate != null && !predicate.test(part)) {
-+ continue;
-+ }
-+
-+ into.add(part);
-+ }
-+ }
-+ }
-+ }
-+ }
-+
-+ public void getEntitiesWithEnderDragonParts(final Entity except, final Class<?> clazz, final AABB box, final List<Entity> into,
-+ final Predicate<? super Entity> predicate) {
-+ if (this.count == 0) {
-+ return;
-+ }
-+
-+ final int minSection = this.manager.minSection;
-+ final int maxSection = this.manager.maxSection;
-+
-+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
-+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
-+
-+ // TODO use the bitset
-+
-+ final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
-+
-+ for (int section = min; section <= max; ++section) {
-+ final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
-+
-+ if (list == null) {
-+ continue;
-+ }
-+
-+ final Entity[] storage = list.storage;
-+
-+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
-+ final Entity entity = storage[i];
-+
-+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) {
-+ continue;
-+ }
-+
-+ if (predicate == null || predicate.test(entity)) {
-+ into.add(entity);
-+ } // else: continue to test the ender dragon parts
-+
-+ if (entity instanceof EnderDragon) {
-+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) {
-+ if (part == except || !part.getBoundingBox().intersects(box) || !clazz.isInstance(part)) {
-+ continue;
-+ }
-+
-+ if (predicate != null && !predicate.test(part)) {
-+ continue;
-+ }
-+
-+ into.add(part);
-+ }
-+ }
-+ }
-+ }
-+ }
-+
-+ public <T extends Entity> void getEntities(final EntityType<?> type, final AABB box, final List<? super T> into,
-+ final Predicate<? super T> predicate) {
-+ if (this.count == 0) {
-+ return;
-+ }
-+
-+ final int minSection = this.manager.minSection;
-+ final int maxSection = this.manager.maxSection;
-+
-+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection);
-+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection);
-+
-+ // TODO use the bitset
-+
-+ final BasicEntityList<Entity>[] entitiesBySection = this.entitiesBySection;
-+
-+ for (int section = min; section <= max; ++section) {
-+ final BasicEntityList<Entity> list = entitiesBySection[section - minSection];
-+
-+ if (list == null) {
-+ continue;
-+ }
-+
-+ final Entity[] storage = list.storage;
-+
-+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) {
-+ final Entity entity = storage[i];
-+
-+ if (entity == null || (type != null && entity.getType() != type) || !entity.getBoundingBox().intersects(box)) {
-+ continue;
-+ }
-+
-+ if (predicate != null && !predicate.test((T)entity)) {
-+ continue;
-+ }
-+
-+ into.add((T)entity);
-+ }
-+ }
-+ }
-+ }
-+}
-diff --git a/src/main/java/io/papermc/paper/world/EntitySliceManager.java b/src/main/java/io/papermc/paper/world/EntitySliceManager.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..3ba094e640d7fe7803e2bbdab8ff3beb6f50e8a0
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/world/EntitySliceManager.java
-@@ -0,0 +1,391 @@
-+package io.papermc.paper.world;
-+
-+import io.papermc.paper.util.CoordinateUtils;
-+import io.papermc.paper.util.WorldUtil;
-+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
-+import net.minecraft.core.BlockPos;
-+import net.minecraft.server.level.ChunkHolder;
-+import net.minecraft.server.level.ServerLevel;
-+import net.minecraft.util.Mth;
-+import net.minecraft.world.entity.Entity;
-+import net.minecraft.world.entity.EntityType;
-+import net.minecraft.world.phys.AABB;
-+import java.util.List;
-+import java.util.concurrent.locks.StampedLock;
-+import java.util.function.Predicate;
-+
-+public final class EntitySliceManager {
-+
-+ protected static final int REGION_SHIFT = 5;
-+ protected static final int REGION_MASK = (1 << REGION_SHIFT) - 1;
-+ protected static final int REGION_SIZE = 1 << REGION_SHIFT;
-+
-+ public final ServerLevel world;
-+
-+ private final StampedLock stateLock = new StampedLock();
-+ protected final Long2ObjectOpenHashMap<ChunkSlicesRegion> regions = new Long2ObjectOpenHashMap<>(64, 0.7f);
-+
-+ private final int minSection; // inclusive
-+ private final int maxSection; // inclusive
-+
-+ protected final Long2ObjectOpenHashMap<ChunkHolder.FullChunkStatus> statusMap = new Long2ObjectOpenHashMap<>();
-+ {
-+ this.statusMap.defaultReturnValue(ChunkHolder.FullChunkStatus.INACCESSIBLE);
-+ }
-+
-+ public EntitySliceManager(final ServerLevel world) {
-+ this.world = world;
-+ this.minSection = WorldUtil.getMinSection(world);
-+ this.maxSection = WorldUtil.getMaxSection(world);
-+ }
-+
-+ public void chunkStatusChange(final int x, final int z, final ChunkHolder.FullChunkStatus newStatus) {
-+ if (newStatus == ChunkHolder.FullChunkStatus.INACCESSIBLE) {
-+ this.statusMap.remove(CoordinateUtils.getChunkKey(x, z));
-+ } else {
-+ this.statusMap.put(CoordinateUtils.getChunkKey(x, z), newStatus);
-+ final ChunkEntitySlices slices = this.getChunk(x, z);
-+ if (slices != null) {
-+ slices.updateStatus(newStatus);
-+ }
-+ }
-+ }
-+
-+ public synchronized void addEntity(final Entity entity) {
-+ final BlockPos pos = entity.blockPosition();
-+ final int sectionX = pos.getX() >> 4;
-+ final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection);
-+ final int sectionZ = pos.getZ() >> 4;
-+ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ);
-+ slices.addEntity(entity, sectionY);
-+
-+ entity.sectionX = sectionX;
-+ entity.sectionY = sectionY;
-+ entity.sectionZ = sectionZ;
-+ }
-+
-+ public synchronized void removeEntity(final Entity entity) {
-+ final ChunkEntitySlices slices = this.getChunk(entity.sectionX, entity.sectionZ);
-+ slices.removeEntity(entity, entity.sectionY);
-+ if (slices.isEmpty()) {
-+ this.removeChunk(entity.sectionX, entity.sectionZ);
-+ }
-+ }
-+
-+ public void moveEntity(final Entity entity) {
-+ final BlockPos newPos = entity.blockPosition();
-+ final int newSectionX = newPos.getX() >> 4;
-+ final int newSectionY = Mth.clamp(newPos.getY() >> 4, this.minSection, this.maxSection);
-+ final int newSectionZ = newPos.getZ() >> 4;
-+
-+ if (newSectionX == entity.sectionX && newSectionY == entity.sectionY && newSectionZ == entity.sectionZ) {
-+ return;
-+ }
-+
-+ synchronized (this) {
-+ // are we changing chunks?
-+ if (newSectionX != entity.sectionX || newSectionZ != entity.sectionZ) {
-+ final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ);
-+ final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ);
-+ synchronized (old) {
-+ old.removeEntity(entity, entity.sectionY);
-+ if (old.isEmpty()) {
-+ this.removeChunk(entity.sectionX, entity.sectionZ);
-+ }
-+ }
-+
-+ synchronized (slices) {
-+ slices.addEntity(entity, newSectionY);
-+
-+ entity.sectionX = newSectionX;
-+ entity.sectionY = newSectionY;
-+ entity.sectionZ = newSectionZ;
-+ }
-+ } else {
-+ final ChunkEntitySlices slices = this.getChunk(newSectionX, newSectionZ);
-+ // same chunk
-+ synchronized (slices) {
-+ slices.removeEntity(entity, entity.sectionY);
-+ slices.addEntity(entity, newSectionY);
-+ }
-+ entity.sectionY = newSectionY;
-+ }
-+ }
-+
-+ }
-+
-+ public void getEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
-+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4;
-+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4;
-+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4;
-+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4;
-+
-+ final int minRegionX = minChunkX >> REGION_SHIFT;
-+ final int minRegionZ = minChunkZ >> REGION_SHIFT;
-+ final int maxRegionX = maxChunkX >> REGION_SHIFT;
-+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
-+
-+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
-+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
-+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
-+
-+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
-+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
-+
-+ if (region == null) {
-+ continue;
-+ }
-+
-+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
-+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
-+
-+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
-+ for (int currX = minX; currX <= maxX; ++currX) {
-+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
-+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
-+ continue;
-+ }
-+
-+ chunk.getEntities(except, box, into, predicate);
-+ }
-+ }
-+ }
-+ }
-+ }
-+
-+ public void getHardCollidingEntities(final Entity except, final AABB box, final List<Entity> into, final Predicate<? super Entity> predicate) {
-+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4;
-+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4;
-+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4;
-+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4;
-+
-+ final int minRegionX = minChunkX >> REGION_SHIFT;
-+ final int minRegionZ = minChunkZ >> REGION_SHIFT;
-+ final int maxRegionX = maxChunkX >> REGION_SHIFT;
-+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
-+
-+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
-+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
-+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
-+
-+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
-+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
-+
-+ if (region == null) {
-+ continue;
-+ }
-+
-+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
-+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
-+
-+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
-+ for (int currX = minX; currX <= maxX; ++currX) {
-+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
-+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
-+ continue;
-+ }
-+
-+ chunk.getHardCollidingEntities(except, box, into, predicate);
-+ }
-+ }
-+ }
-+ }
-+ }
-+
-+ public <T extends Entity> void getEntities(final EntityType<?> type, final AABB box, final List<? super T> into,
-+ final Predicate<? super T> predicate) {
-+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4;
-+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4;
-+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4;
-+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4;
-+
-+ final int minRegionX = minChunkX >> REGION_SHIFT;
-+ final int minRegionZ = minChunkZ >> REGION_SHIFT;
-+ final int maxRegionX = maxChunkX >> REGION_SHIFT;
-+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
-+
-+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
-+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
-+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
-+
-+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
-+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
-+
-+ if (region == null) {
-+ continue;
-+ }
-+
-+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
-+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
-+
-+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
-+ for (int currX = minX; currX <= maxX; ++currX) {
-+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
-+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
-+ continue;
-+ }
-+
-+ chunk.getEntities(type, box, (List)into, (Predicate)predicate);
-+ }
-+ }
-+ }
-+ }
-+ }
-+
-+ public <T extends Entity> void getEntities(final Class<? extends T> clazz, final Entity except, final AABB box, final List<? super T> into,
-+ final Predicate<? super T> predicate) {
-+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4;
-+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4;
-+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4;
-+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4;
-+
-+ final int minRegionX = minChunkX >> REGION_SHIFT;
-+ final int minRegionZ = minChunkZ >> REGION_SHIFT;
-+ final int maxRegionX = maxChunkX >> REGION_SHIFT;
-+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT;
-+
-+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) {
-+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0;
-+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK;
-+
-+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) {
-+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ);
-+
-+ if (region == null) {
-+ continue;
-+ }
-+
-+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0;
-+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK;
-+
-+ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
-+ for (int currX = minX; currX <= maxX; ++currX) {
-+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT));
-+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) {
-+ continue;
-+ }
-+
-+ chunk.getEntities(clazz, except, box, into, predicate);
-+ }
-+ }
-+ }
-+ }
-+ }
-+
-+ public ChunkEntitySlices getChunk(final int chunkX, final int chunkZ) {
-+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT);
-+ if (region == null) {
-+ return null;
-+ }
-+
-+ return region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT));
-+ }
-+
-+ public ChunkEntitySlices getOrCreateChunk(final int chunkX, final int chunkZ) {
-+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT);
-+ ChunkEntitySlices ret;
-+ if (region == null || (ret = region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT))) == null) {
-+ ret = new ChunkEntitySlices(this.world, chunkX, chunkZ, this.statusMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)),
-+ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world));
-+
-+ this.addChunk(chunkX, chunkZ, ret);
-+
-+ return ret;
-+ }
-+
-+ return ret;
-+ }
-+
-+ public ChunkSlicesRegion getRegion(final int regionX, final int regionZ) {
-+ final long key = CoordinateUtils.getChunkKey(regionX, regionZ);
-+ final long attempt = this.stateLock.tryOptimisticRead();
-+ if (attempt != 0L) {
-+ try {
-+ final ChunkSlicesRegion ret = this.regions.get(key);
-+
-+ if (this.stateLock.validate(attempt)) {
-+ return ret;
-+ }
-+ } catch (final Error error) {
-+ throw error;
-+ } catch (final Throwable thr) {
-+ // ignore
-+ }
-+ }
-+
-+ this.stateLock.readLock();
-+ try {
-+ return this.regions.get(key);
-+ } finally {
-+ this.stateLock.tryUnlockRead();
-+ }
-+ }
-+
-+ public synchronized void removeChunk(final int chunkX, final int chunkZ) {
-+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT);
-+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT);
-+
-+ final ChunkSlicesRegion region = this.regions.get(key);
-+ final int remaining = region.remove(relIndex);
-+
-+ if (remaining == 0) {
-+ this.stateLock.writeLock();
-+ try {
-+ this.regions.remove(key);
-+ } finally {
-+ this.stateLock.tryUnlockWrite();
-+ }
-+ }
-+ }
-+
-+ public synchronized void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) {
-+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT);
-+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT);
-+
-+ ChunkSlicesRegion region = this.regions.get(key);
-+ if (region != null) {
-+ region.add(relIndex, slices);
-+ } else {
-+ region = new ChunkSlicesRegion();
-+ region.add(relIndex, slices);
-+ this.stateLock.writeLock();
-+ try {
-+ this.regions.put(key, region);
-+ } finally {
-+ this.stateLock.tryUnlockWrite();
-+ }
-+ }
-+ }
-+
-+ public static final class ChunkSlicesRegion {
-+
-+ protected final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE];
-+ protected int sliceCount;
-+
-+ public ChunkEntitySlices get(final int index) {
-+ return this.slices[index];
-+ }
-+
-+ public int remove(final int index) {
-+ final ChunkEntitySlices slices = this.slices[index];
-+ if (slices == null) {
-+ throw new IllegalStateException();
-+ }
-+
-+ this.slices[index] = null;
-+
-+ return --this.sliceCount;
-+ }
-+
-+ public void add(final int index, final ChunkEntitySlices slices) {
-+ final ChunkEntitySlices curr = this.slices[index];
-+ if (curr != null) {
-+ throw new IllegalStateException();
-+ }
-+
-+ this.slices[index] = slices;
-+
-+ ++this.sliceCount;
-+ }
-+ }
-+}
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 39d96b4e3dce6d67b568b7b00456de164f6a7241..29b80d074600fa7e20b05d1fe70ac12969b954a4 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -454,7 +454,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
- DataFixer datafixer = minecraftserver.getFixerUpper();
- EntityPersistentStorage<Entity> entitypersistentstorage = new EntityStorage(this, convertable_conversionsession.getDimensionPath(resourcekey).resolve("entities"), datafixer, flag2, minecraftserver);
-
-- this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage);
-+ this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Paper
- StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager();
- int j = this.spigotConfig.viewDistance; // Spigot
- int k = this.spigotConfig.simulationDistance; // Spigot
-diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
-index 32d6e4b194c3c4eca7009059f8d185896b5ae556..51d3150e732f95be13f5f54d994dab1fa89ed3f2 100644
---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java
-+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java
-@@ -498,4 +498,21 @@ public class WorldGenRegion implements WorldGenLevel {
- public long nextSubTickCount() {
- return this.subTickCount.getAndIncrement();
- }
-+
-+ // Paper start
-+ // No-op, this class doesn't provide entity access
-+ @Override
-+ public List<Entity> getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate) {
-+ return Collections.emptyList();
-+ }
-+
-+ @Override
-+ public void getEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {}
-+
-+ @Override
-+ public void getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {}
-+
-+ @Override
-+ public <T> void getEntitiesByClass(Class<? extends T> clazz, Entity except, AABB box, List<? super T> into, Predicate<? super T> predicate) {}
-+ // Paper end
- }
-diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 5936fb0d27266e0e91eae5cf49e15f67aeb208bf..f8458173dd717ca5fd9d265dcdaee0f0ef1a833c 100644
---- a/src/main/java/net/minecraft/world/entity/Entity.java
-+++ b/src/main/java/net/minecraft/world/entity/Entity.java
-@@ -490,6 +490,56 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- }
- // Paper end - make end portalling safe
-
-+ // Paper start
-+ /**
-+ * Overriding this field will cause memory leaks.
-+ */
-+ private final boolean hardCollides;
-+
-+ private static final java.util.Map<Class<? extends Entity>, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>());
-+ {
-+ /* // Goodbye, broken on reobf...
-+ Boolean hardCollides = cachedOverrides.get(this.getClass());
-+ if (hardCollides == null) {
-+ try {
-+ java.lang.reflect.Method getHardCollisionBoxEntityMethod = Entity.class.getMethod("canCollideWith", Entity.class);
-+ java.lang.reflect.Method hasHardCollisionBoxMethod = Entity.class.getMethod("canBeCollidedWith");
-+ if (!this.getClass().getMethod(hasHardCollisionBoxMethod.getName(), hasHardCollisionBoxMethod.getParameterTypes()).equals(hasHardCollisionBoxMethod)
-+ || !this.getClass().getMethod(getHardCollisionBoxEntityMethod.getName(), getHardCollisionBoxEntityMethod.getParameterTypes()).equals(getHardCollisionBoxEntityMethod)) {
-+ hardCollides = Boolean.TRUE;
-+ } else {
-+ hardCollides = Boolean.FALSE;
-+ }
-+ cachedOverrides.put(this.getClass(), hardCollides);
-+ }
-+ catch (ThreadDeath thr) { throw thr; }
-+ catch (Throwable thr) {
-+ // shouldn't happen, just explode
-+ throw new RuntimeException(thr);
-+ }
-+ } */
-+ this.hardCollides = this instanceof Boat
-+ || this instanceof net.minecraft.world.entity.monster.Shulker
-+ || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart
-+ || this.shouldHardCollide();
-+ }
-+
-+ // plugins can override
-+ protected boolean shouldHardCollide() {
-+ return false;
-+ }
-+
-+ public final boolean hardCollides() {
-+ return this.hardCollides;
-+ }
-+
-+ public net.minecraft.server.level.ChunkHolder.FullChunkStatus chunkStatus;
-+
-+ public int sectionX = Integer.MIN_VALUE;
-+ public int sectionY = Integer.MIN_VALUE;
-+ public int sectionZ = Integer.MIN_VALUE;
-+ // Paper end
-+
- public Entity(EntityType<?> type, Level world) {
- this.id = Entity.ENTITY_COUNTER.incrementAndGet();
- this.passengers = ImmutableList.of();
-@@ -2372,11 +2422,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource {
- return InteractionResult.PASS;
- }
-
-- public boolean canCollideWith(Entity other) {
-+ public boolean canCollideWith(Entity other) { // Paper - diff on change, hard colliding entities override this - TODO CHECK ON UPDATE - AbstractMinecart/Boat override
- return other.canBeCollidedWith() && !this.isPassengerOfSameVehicle(other);
- }
-
-- public boolean canBeCollidedWith() {
-+ public boolean canBeCollidedWith() { // Paper - diff on change, hard colliding entities override this TODO CHECK ON UPDATE - Boat/Shulker override
- return false;
- }
-
-diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java
-index 1a3be6f0570c7c746eafa36544debe90d7629432..c0817ef8927f00e2fd3fbf3289f8041fcb494049 100644
---- a/src/main/java/net/minecraft/world/level/EntityGetter.java
-+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java
-@@ -18,6 +18,18 @@ import net.minecraft.world.phys.shapes.Shapes;
- import net.minecraft.world.phys.shapes.VoxelShape;
-
- public interface EntityGetter {
-+
-+ // Paper start
-+ List<Entity> getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate);
-+
-+ void getEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into);
-+
-+ void getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into);
-+
-+ <T> void getEntitiesByClass(Class<? extends T> clazz, Entity except, final AABB box, List<? super T> into,
-+ Predicate<? super T> predicate);
-+ // Paper end
-+
- List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate);
-
- <T extends Entity> List<T> getEntities(EntityTypeTest<Entity, T> filter, AABB box, Predicate<? super T> predicate);
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 4b7ed29a7a38cf7f8fb488c9c34471fafcdf2f25..a04517137ab9deff215b6f9b9ee405500af0e393 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -293,6 +293,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- this.entityLimiter = new org.spigotmc.TickLimiter(spigotConfig.entityMaxTickTime);
- this.tileLimiter = new org.spigotmc.TickLimiter(spigotConfig.tileMaxTickTime);
- this.chunkPacketBlockController = this.paperConfig().anticheat.antiXray.enabled ? new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray
-+ this.entitySliceManager = new io.papermc.paper.world.EntitySliceManager((ServerLevel)this); // Paper
- }
-
- // Paper start
-@@ -968,26 +969,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- public List<Entity> getEntities(@Nullable Entity except, AABB box, Predicate<? super Entity> predicate) {
- this.getProfiler().incrementCounter("getEntities");
- List<Entity> list = Lists.newArrayList();
--
-- this.getEntities().get(box, (entity1) -> {
-- if (entity1 != except && predicate.test(entity1)) {
-- list.add(entity1);
-- }
--
-- if (entity1 instanceof EnderDragon) {
-- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity1).getSubEntities();
-- int i = aentitycomplexpart.length;
--
-- for (int j = 0; j < i; ++j) {
-- EnderDragonPart entitycomplexpart = aentitycomplexpart[j];
--
-- if (entity1 != except && predicate.test(entitycomplexpart)) {
-- list.add(entitycomplexpart);
-- }
-- }
-- }
--
-- }, predicate == net.minecraft.world.entity.EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper
-+ this.entitySliceManager.getEntities(except, box, list, predicate); // Paper - optimise this call
- return list;
- }
-
-@@ -996,27 +978,22 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- this.getProfiler().incrementCounter("getEntities");
- List<T> list = Lists.newArrayList();
-
-- this.getEntities().get(filter, box, (entity) -> {
-- if (predicate.test(entity)) {
-- list.add(entity);
-- }
--
-- if (entity instanceof EnderDragon) {
-- EnderDragon entityenderdragon = (EnderDragon) entity;
-- EnderDragonPart[] aentitycomplexpart = entityenderdragon.getSubEntities();
-- int i = aentitycomplexpart.length;
--
-- for (int j = 0; j < i; ++j) {
-- EnderDragonPart entitycomplexpart = aentitycomplexpart[j];
-- T t0 = filter.tryCast(entitycomplexpart); // CraftBukkit - decompile error
--
-- if (t0 != null && predicate.test(t0)) {
-- list.add(t0);
-- }
-- }
-+ // Paper start - optimise this call
-+ if (filter instanceof net.minecraft.world.entity.EntityType) {
-+ this.entitySliceManager.getEntities((net.minecraft.world.entity.EntityType)filter, box, list, predicate);
-+ } else {
-+ Predicate<? super T> test = (obj) -> {
-+ return filter.tryCast(obj) != null;
-+ };
-+ predicate = predicate == null ? test : test.and((Predicate)predicate);
-+ Class base;
-+ if (filter == null || (base = filter.getBaseClass()) == null || base == Entity.class) {
-+ this.entitySliceManager.getEntities((Entity) null, box, (List)list, (Predicate)predicate);
-+ } else {
-+ this.entitySliceManager.getEntities(base, null, box, (List)list, (Predicate)predicate); // Paper - optimise this call
- }
--
-- });
-+ }
-+ // Paper end - optimise this call
- return list;
- }
-
-@@ -1343,4 +1320,46 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
- public long nextSubTickCount() {
- return (long) (this.subTickCount++);
- }
-+
-+ // Paper start
-+ protected final io.papermc.paper.world.EntitySliceManager entitySliceManager;
-+
-+ public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) {
-+ io.papermc.paper.world.ChunkEntitySlices slices = this.entitySliceManager.getChunk(chunkX, chunkZ);
-+ if (slices == null) {
-+ return new org.bukkit.entity.Entity[0];
-+ }
-+ return slices.getChunkEntities();
-+ }
-+
-+ @Override
-+ public List<Entity> getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate) {
-+ List<Entity> ret = new java.util.ArrayList<>();
-+ this.entitySliceManager.getEntities(except, box, ret, predicate);
-+ return ret;
-+ }
-+
-+ @Override
-+ public void getEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {
-+ this.entitySliceManager.getEntities(except, box, into, predicate);
-+ }
-+
-+ @Override
-+ public void getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {
-+ this.entitySliceManager.getHardCollidingEntities(except, box, into, predicate);
-+ }
-+
-+ @Override
-+ public <T> void getEntitiesByClass(Class<? extends T> clazz, Entity except, final AABB box, List<? super T> into,
-+ Predicate<? super T> predicate) {
-+ this.entitySliceManager.getEntities((Class)clazz, except, box, (List)into, (Predicate)predicate);
-+ }
-+
-+ @Override
-+ public <T extends Entity> List<T> getEntitiesOfClass(Class<T> entityClass, AABB box, Predicate<? super T> predicate) {
-+ List<T> ret = new java.util.ArrayList<>();
-+ this.entitySliceManager.getEntities(entityClass, null, box, ret, predicate);
-+ return ret;
-+ }
-+ // Paper end
- }
-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 a1a52669c19af22e3b5267d43584cb00d1646453..f635b610e68d129aa0ae60c54b83da6943946436 100644
---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
-+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java
-@@ -49,8 +49,10 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
- private final Long2ObjectMap<PersistentEntitySectionManager.ChunkLoadStatus> chunkLoadStatuses = new Long2ObjectOpenHashMap();
- private final LongSet chunksToUnload = new LongOpenHashSet();
- private final Queue<ChunkEntities<T>> loadingInbox = Queues.newConcurrentLinkedQueue();
-+ public final io.papermc.paper.world.EntitySliceManager entitySliceManager; // Paper
-
-- public PersistentEntitySectionManager(Class<T> entityClass, LevelCallback<T> handler, EntityPersistentStorage<T> dataAccess) {
-+ public PersistentEntitySectionManager(Class<T> entityClass, LevelCallback<T> handler, EntityPersistentStorage<T> dataAccess, io.papermc.paper.world.EntitySliceManager entitySliceManager) { // Paper
-+ this.entitySliceManager = entitySliceManager; // Paper
- this.sectionStorage = new EntitySectionStorage<>(entityClass, this.chunkVisibility);
- this.chunkVisibility.defaultReturnValue(Visibility.HIDDEN);
- this.chunkLoadStatuses.defaultReturnValue(PersistentEntitySectionManager.ChunkLoadStatus.FRESH);
-@@ -124,6 +126,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
- EntitySection<T> entitysection = this.sectionStorage.getOrCreateSection(i);
-
- entitysection.add(entity);
-+ this.entitySliceManager.addEntity((Entity)entity); // Paper
- entity.setLevelCallback(new PersistentEntitySectionManager.Callback(entity, i, entitysection));
- if (!existing) {
- this.callbacks.onCreated(entity);
-@@ -181,6 +184,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
- io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous chunk ticking status update"); // Paper
- Visibility visibility = Visibility.fromFullChunkStatus(levelType);
-
-+ this.entitySliceManager.chunkStatusChange(chunkPos.x, chunkPos.z, levelType); // Paper
- this.updateChunkStatus(chunkPos, visibility);
- }
-
-@@ -467,6 +471,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
- long i = SectionPos.asLong(blockposition);
-
- if (i != this.currentSectionKey) {
-+ PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Paper
- Visibility visibility = this.currentSection.getStatus();
-
- if (!this.currentSection.remove(this.entity)) {
-@@ -524,6 +529,7 @@ public class PersistentEntitySectionManager<T extends EntityAccess> implements A
- 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});
- }
-+ PersistentEntitySectionManager.this.entitySliceManager.removeEntity((Entity)this.entity); // Paper
-
- Visibility visibility = PersistentEntitySectionManager.getEffectiveStatus(this.entity, this.currentSection.getStatus());
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-index d9c2e7e18e1ede37d92cecb8ddb32dae1472bd1c..2224b9cfbc3be59037ef49ce278989ea3a710bb5 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-@@ -134,9 +134,7 @@ public class CraftChunk implements Chunk {
- long pair = ChunkPos.asLong(x, z);
-
- if (entityManager.areEntitiesLoaded(pair)) {
-- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream()
-- .map(net.minecraft.world.entity.Entity::getBukkitEntity)
-- .filter(Objects::nonNull).toArray(Entity[]::new);
-+ return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this
- }
-
- entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading
-@@ -172,9 +170,7 @@ public class CraftChunk implements Chunk {
- }
- }
-
-- return entityManager.getEntities(new ChunkPos(this.x, this.z)).stream()
-- .map(net.minecraft.world.entity.Entity::getBukkitEntity)
-- .filter(Objects::nonNull).toArray(Entity[]::new);
-+ return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this
- }
-
- @Override
-diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
-index 3bedc22c253c3632b5624c05e78ed3671e5d30ce..fbd82b6be6604bf854e01ed5718e4e072f42b265 100644
---- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
-+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java
-@@ -254,4 +254,20 @@ public class DummyGeneratorAccess implements WorldGenLevel {
- public boolean destroyBlock(BlockPos pos, boolean drop, Entity breakingEntity, int maxUpdateDepth) {
- return false; // SPIGOT-6515
- }
-+
-+ // Paper start
-+ @Override
-+ public List<Entity> getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate) {
-+ return java.util.Collections.emptyList();
-+ }
-+
-+ @Override
-+ public void getEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {}
-+
-+ @Override
-+ public void getHardCollidingEntities(Entity except, AABB box, Predicate<? super Entity> predicate, List<Entity> into) {}
-+
-+ @Override
-+ public <T> void getEntitiesByClass(Class<? extends T> clazz, Entity except, AABB box, List<? super T> into, Predicate<? super T> predicate) {}
-+ // Paper end
- }
-diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java
-index 0508f43ad396679d3372ae4caf029086a1524109..b1ed97618d08d7691d24f89e9e9b0ed0f2bddd09 100644
---- a/src/main/java/org/spigotmc/ActivationRange.java
-+++ b/src/main/java/org/spigotmc/ActivationRange.java
-@@ -211,7 +211,13 @@ public class ActivationRange
- ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, worldHeight, villagerActivationRange );
- // Paper end
-
-- world.getEntities().get(maxBB, ActivationRange::activateEntity);
-+ // Paper start
-+ java.util.List<Entity> entities = world.getEntities((Entity)null, maxBB, null);
-+ for (int i = 0; i < entities.size(); i++) {
-+ Entity entity = entities.get(i);
-+ ActivationRange.activateEntity(entity);
-+ }
-+ // Paper end
- }
- MinecraftTimings.entityActivationCheckTimer.stopTiming();
- }
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0754-Allow-removal-addition-of-entities-to-entity-ticklis.patch b/patches/removed/1.19.2-legacy-chunksystem/0754-Allow-removal-addition-of-entities-to-entity-ticklis.patch
deleted file mode 100644
index bf79d6270e..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0754-Allow-removal-addition-of-entities-to-entity-ticklis.patch
+++ /dev/null
@@ -1,89 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sat, 19 Jun 2021 22:47:17 -0700
-Subject: [PATCH] Allow removal/addition of entities to entity ticklist during
- tick
-
-It really doesn't make any sense that we would iterate over removed
-entities during tick. Sure - tick entity checks removed, but
-does it check if the entity is in an entity ticking chunk?
-No it doesn't. So, allowing removal while iteration
-ENSURES only entities MARKED TO TICK are ticked.
-
-diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
-index a176a886235494fdc722030a93658d361bf50f03..4cdfc433df67afcd455422e9baf56f167dd712ae 100644
---- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
-+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java
-@@ -8,57 +8,42 @@ import javax.annotation.Nullable;
- import net.minecraft.world.entity.Entity;
-
- public class EntityTickList {
-- private Int2ObjectMap<Entity> active = new Int2ObjectLinkedOpenHashMap<>();
-- private Int2ObjectMap<Entity> passive = new Int2ObjectLinkedOpenHashMap<>();
-- @Nullable
-- private Int2ObjectMap<Entity> iterated;
-+ private final io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<Entity> entities = new io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Paper - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking?
-
- private void ensureActiveIsNotIterated() {
-- if (this.iterated == this.active) {
-- this.passive.clear();
--
-- for(Int2ObjectMap.Entry<Entity> entry : Int2ObjectMaps.fastIterable(this.active)) {
-- this.passive.put(entry.getIntKey(), entry.getValue());
-- }
--
-- Int2ObjectMap<Entity> int2ObjectMap = this.active;
-- this.active = this.passive;
-- this.passive = int2ObjectMap;
-- }
-+ // Paper - replace with better logic, do not delay removals
-
- }
-
- public void add(Entity entity) {
- io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist addition"); // Paper
- this.ensureActiveIsNotIterated();
-- this.active.put(entity.getId(), entity);
-+ this.entities.add(entity); // Paper - replace with better logic, do not delay removals/additions
- }
-
- public void remove(Entity entity) {
- io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist removal"); // Paper
- this.ensureActiveIsNotIterated();
-- this.active.remove(entity.getId());
-+ this.entities.remove(entity); // Paper - replace with better logic, do not delay removals/additions
- }
-
- public boolean contains(Entity entity) {
-- return this.active.containsKey(entity.getId());
-+ return this.entities.contains(entity); // Paper - replace with better logic, do not delay removals/additions
- }
-
- public void forEach(Consumer<Entity> action) {
- io.papermc.paper.util.TickThread.ensureTickThread("Asynchronous entity ticklist iteration"); // Paper
-- if (this.iterated != null) {
-- throw new UnsupportedOperationException("Only one concurrent iteration supported");
-- } else {
-- this.iterated = this.active;
--
-- try {
-- for(Entity entity : this.active.values()) {
-- action.accept(entity);
-- }
-- } finally {
-- this.iterated = null;
-+ // Paper start - replace with better logic, do not delay removals/additions
-+ // To ensure nothing weird happens with dimension travelling, do not iterate over new entries...
-+ // (by dfl iterator() is configured to not iterate over new entries)
-+ io.papermc.paper.util.maplist.IteratorSafeOrderedReferenceSet.Iterator<Entity> iterator = this.entities.iterator();
-+ try {
-+ while (iterator.hasNext()) {
-+ action.accept(iterator.next());
- }
--
-+ } finally {
-+ iterator.finishedIterating();
- }
-+ // Paper end - replace with better logic, do not delay removals/additions
- }
- }
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0763-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch b/patches/removed/1.19.2-legacy-chunksystem/0763-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch
deleted file mode 100644
index 33087ceabe..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0763-Do-not-process-entity-loads-in-CraftChunk-getEntitie.patch
+++ /dev/null
@@ -1,68 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Fri, 3 Sep 2021 15:50:25 +0100
-Subject: [PATCH] Do not process entity loads in CraftChunk#getEntities
-
-This re-introduces the issue behind #5872 but fixes #6543
-The logic here is generally flawed however somewhat of a nuance,
-upstream uses managedBlock which is basically needed to process
-the posted entity adds, but, has the side-effect of processing any
-chunk loads which has the naunce of stacking up and either causing a
-massive performance hit, or can potentially lead the server to crash.
-
-This issue is particularly noticable on paper due to the cumulative efforts
-to drastically improve chunk loading speeds which means that there is much more
-of a chance that we're about to eat a dirtload of chunk load callbacks, thus
-making this issue much more of an issue
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-index 2224b9cfbc3be59037ef49ce278989ea3a710bb5..0b9312dc5ee43d2d450dc6e9f07a9ac0320955ca 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
-@@ -130,46 +130,6 @@ public class CraftChunk implements Chunk {
- this.getWorld().getChunkAt(x, z); // Transient load for this tick
- }
-
-- PersistentEntitySectionManager<net.minecraft.world.entity.Entity> entityManager = this.getCraftWorld().getHandle().entityManager;
-- long pair = ChunkPos.asLong(x, z);
--
-- if (entityManager.areEntitiesLoaded(pair)) {
-- return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this
-- }
--
-- entityManager.ensureChunkQueuedForLoad(pair); // Start entity loading
--
-- // SPIGOT-6772: Use entity mailbox and re-schedule entities if they get unloaded
-- ProcessorMailbox<Runnable> mailbox = ((EntityStorage) entityManager.permanentStorage).entityDeserializerQueue;
-- BooleanSupplier supplier = () -> {
-- // only execute inbox if our entities are not present
-- if (entityManager.areEntitiesLoaded(pair)) {
-- return true;
-- }
--
-- if (!entityManager.isPending(pair)) {
-- // Our entities got unloaded, this should normally not happen.
-- entityManager.ensureChunkQueuedForLoad(pair); // Re-start entity loading
-- }
--
-- // tick loading inbox, which loads the created entities to the world
-- // (if present)
-- entityManager.tick();
-- // check if our entities are loaded
-- return entityManager.areEntitiesLoaded(pair);
-- };
--
-- // now we wait until the entities are loaded,
-- // the converting from NBT to entity object is done on the main Thread which is why we wait
-- while (!supplier.getAsBoolean()) {
-- if (mailbox.size() != 0) {
-- mailbox.run();
-- } else {
-- Thread.yield();
-- LockSupport.parkNanos("waiting for entity loading", 100000L);
-- }
-- }
--
- return getCraftWorld().getHandle().getChunkEntities(this.x, this.z); // Paper - optimise this
- }
-
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0801-Actually-unload-POI-data.patch b/patches/removed/1.19.2-legacy-chunksystem/0801-Actually-unload-POI-data.patch
deleted file mode 100644
index bf5d29c068..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0801-Actually-unload-POI-data.patch
+++ /dev/null
@@ -1,321 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 31 Aug 2020 11:08:17 -0700
-Subject: [PATCH] Actually unload POI data
-
-While it's not likely for a poi data leak to be meaningful,
-sometimes it is.
-
-This patch also prevents the saving/unloading of POI data when
-world saving is disabled.
-
-diff --git a/src/main/java/net/minecraft/server/ChunkSystem.java b/src/main/java/net/minecraft/server/ChunkSystem.java
-index 2a099fe0d514f181bf2b452d5333bc29b0d29e43..81ea64443a843736f9ada97900d173c302e39ba0 100644
---- a/src/main/java/net/minecraft/server/ChunkSystem.java
-+++ b/src/main/java/net/minecraft/server/ChunkSystem.java
-@@ -270,6 +270,7 @@ public final class ChunkSystem {
- for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) {
- chunkMap.regionManagers.get(index).addChunk(holder.pos.x, holder.pos.z);
- }
-+ chunkMap.getPoiManager().dequeueUnload(holder.pos.longKey); // Paper - unload POI data
- }
-
- public static void onChunkHolderDelete(final ServerLevel level, final ChunkHolder holder) {
-@@ -277,6 +278,7 @@ public final class ChunkSystem {
- for (int index = 0, len = chunkMap.regionManagers.size(); index < len; ++index) {
- chunkMap.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
- }
-+ chunkMap.getPoiManager().queueUnload(holder.pos.longKey, MinecraftServer.currentTickLong + 1); // Paper - unload POI data
- }
-
- public static void onChunkBorder(LevelChunk chunk, ChunkHolder holder) {
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index fe10c770b511fa8a38ece2bf9679492a85b28eff..a5e74d30045a171f5ed66a115fbd429e9ab412af 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -1016,7 +1016,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- private void processUnloads(BooleanSupplier shouldKeepTicking) {
- LongIterator longiterator = this.toDrop.iterator();
-- for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) {
-+ for (int i = 0; longiterator.hasNext() && (shouldKeepTicking.getAsBoolean() || i < 200 || this.toDrop.size() > 2000); longiterator.remove()) { // Paper - diff on change
- long j = longiterator.nextLong();
- ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
-
-@@ -1164,6 +1164,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
- this.poiManager.loadInData(pos, chunkHolder.poiData);
- chunkHolder.tasks.forEach(Runnable::run);
-+ this.getPoiManager().dequeueUnload(pos.longKey); // Paper
-
- if (chunkHolder.protoChunk != null) {
- ProtoChunk protochunk = chunkHolder.protoChunk;
-diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-index 497a81e49d54380713c18523ae8f09f94c453721..210b0cdd4831421c8f43c3d823ac8e962b56bbbc 100644
---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java
-@@ -1,5 +1,6 @@
- package net.minecraft.world.entity.ai.village.poi;
-
-+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper
- import com.mojang.datafixers.DataFixer;
- import com.mojang.datafixers.util.Pair;
- import it.unimi.dsi.fastutil.longs.Long2ByteMap;
-@@ -38,16 +39,145 @@ import net.minecraft.world.level.chunk.storage.SectionStorage;
- public class PoiManager extends SectionStorage<PoiSection> {
- public static final int MAX_VILLAGE_DISTANCE = 6;
- public static final int VILLAGE_SECTION_SIZE = 1;
-- private final PoiManager.DistanceTracker distanceTracker;
-+ // Paper start - unload poi data
-+ // the vanilla tracker needs to be replaced because it does not support level removes
-+ private final io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D villageDistanceTracker = new io.papermc.paper.util.misc.Delayed26WayDistancePropagator3D();
-+ static final int POI_DATA_SOURCE = 7;
-+ public static int convertBetweenLevels(final int level) {
-+ return POI_DATA_SOURCE - level;
-+ }
-+
-+ protected void updateDistanceTracking(long section) {
-+ if (this.isVillageCenter(section)) {
-+ this.villageDistanceTracker.setSource(section, POI_DATA_SOURCE);
-+ } else {
-+ this.villageDistanceTracker.removeSource(section);
-+ }
-+ }
-+ // Paper end - unload poi data
- private final LongSet loadedChunks = new LongOpenHashSet();
- public final net.minecraft.server.level.ServerLevel world; // Paper // Paper public
-
- public PoiManager(Path path, DataFixer dataFixer, boolean dsync, RegistryAccess registryManager, LevelHeightAccessor world) {
- super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, registryManager, world);
-+ if (world == null) { throw new IllegalStateException("world must be non-null"); } // Paper - require non-null
- this.world = (net.minecraft.server.level.ServerLevel)world; // Paper
-- this.distanceTracker = new PoiManager.DistanceTracker();
- }
-
-+ // Paper start - actually unload POI data
-+ private final java.util.TreeSet<QueuedUnload> queuedUnloads = new java.util.TreeSet<>();
-+ private final Long2ObjectOpenHashMap<QueuedUnload> queuedUnloadsByCoordinate = new Long2ObjectOpenHashMap<>();
-+
-+ static final class QueuedUnload implements Comparable<QueuedUnload> {
-+
-+ private final long unloadTick;
-+ private final long coordinate;
-+
-+ public QueuedUnload(long unloadTick, long coordinate) {
-+ this.unloadTick = unloadTick;
-+ this.coordinate = coordinate;
-+ }
-+
-+ @Override
-+ public int compareTo(QueuedUnload other) {
-+ if (other.unloadTick == this.unloadTick) {
-+ return Long.compare(this.coordinate, other.coordinate);
-+ } else {
-+ return Long.compare(this.unloadTick, other.unloadTick);
-+ }
-+ }
-+
-+ @Override
-+ public int hashCode() {
-+ int hash = 1;
-+ hash = hash * 31 + Long.hashCode(this.unloadTick);
-+ hash = hash * 31 + Long.hashCode(this.coordinate);
-+ return hash;
-+ }
-+
-+ @Override
-+ public boolean equals(Object obj) {
-+ if (obj == null || obj.getClass() != QueuedUnload.class) {
-+ return false;
-+ }
-+ QueuedUnload other = (QueuedUnload)obj;
-+ return other.unloadTick == this.unloadTick && other.coordinate == this.coordinate;
-+ }
-+ }
-+
-+ long determineDelay(long coordinate) {
-+ if (this.isEmpty(coordinate)) {
-+ return 5 * 60 * 20;
-+ } else {
-+ return 60 * 20;
-+ }
-+ }
-+
-+ public void queueUnload(long coordinate, long minTarget) {
-+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload queue");
-+ QueuedUnload unload = new QueuedUnload(minTarget + this.determineDelay(coordinate), coordinate);
-+ QueuedUnload existing = this.queuedUnloadsByCoordinate.put(coordinate, unload);
-+ if (existing != null) {
-+ this.queuedUnloads.remove(existing);
-+ }
-+ this.queuedUnloads.add(unload);
-+ }
-+
-+ public void dequeueUnload(long coordinate) {
-+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload dequeue");
-+ QueuedUnload unload = this.queuedUnloadsByCoordinate.remove(coordinate);
-+ if (unload != null) {
-+ this.queuedUnloads.remove(unload);
-+ }
-+ }
-+
-+ public void pollUnloads(BooleanSupplier canSleepForTick) {
-+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload");
-+ long currentTick = net.minecraft.server.MinecraftServer.currentTickLong;
-+ net.minecraft.server.level.ServerChunkCache chunkProvider = this.world.getChunkSource();
-+ net.minecraft.server.level.ChunkMap playerChunkMap = chunkProvider.chunkMap;
-+ // copied target determination from PlayerChunkMap
-+
-+ java.util.Iterator<QueuedUnload> iterator = this.queuedUnloads.iterator();
-+ for (int i = 0; iterator.hasNext() && (i < 200 || this.queuedUnloads.size() > 2000 || canSleepForTick.getAsBoolean()); i++) {
-+ QueuedUnload unload = iterator.next();
-+ if (unload.unloadTick > currentTick) {
-+ break;
-+ }
-+
-+ long coordinate = unload.coordinate;
-+
-+ iterator.remove();
-+ this.queuedUnloadsByCoordinate.remove(coordinate);
-+
-+ if (playerChunkMap.getUnloadingChunkHolder(net.minecraft.server.MCUtil.getCoordinateX(coordinate), net.minecraft.server.MCUtil.getCoordinateZ(coordinate)) != null
-+ || playerChunkMap.getUpdatingChunkIfPresent(coordinate) != null) {
-+ continue;
-+ }
-+
-+ this.unloadData(coordinate);
-+ }
-+ }
-+
-+ @Override
-+ public void unloadData(long coordinate) {
-+ io.papermc.paper.util.TickThread.softEnsureTickThread("async unloading poi data");
-+ super.unloadData(coordinate);
-+ }
-+
-+ @Override
-+ protected void onUnload(long coordinate) {
-+ io.papermc.paper.util.TickThread.softEnsureTickThread("async poi unload callback");
-+ this.loadedChunks.remove(coordinate);
-+ int chunkX = net.minecraft.server.MCUtil.getCoordinateX(coordinate);
-+ int chunkZ = net.minecraft.server.MCUtil.getCoordinateZ(coordinate);
-+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
-+ long sectionPos = SectionPos.asLong(chunkX, section, chunkZ);
-+ this.updateDistanceTracking(sectionPos);
-+ }
-+ }
-+ // Paper end - actually unload POI data
-+
- public void add(BlockPos pos, Holder<PoiType> type) {
- this.getOrCreate(SectionPos.asLong(pos)).add(pos, type);
- }
-@@ -201,8 +331,8 @@ public class PoiManager extends SectionStorage<PoiSection> {
- }
-
- public int sectionsToVillage(SectionPos pos) {
-- this.distanceTracker.runAllUpdates();
-- return this.distanceTracker.getLevel(pos.asLong());
-+ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking util
-+ return convertBetweenLevels(this.villageDistanceTracker.getLevel(io.papermc.paper.util.CoordinateUtils.getChunkSectionKey(pos))); // Paper - replace distance tracking util
- }
-
- boolean isVillageCenter(long pos) {
-@@ -217,7 +347,7 @@ public class PoiManager extends SectionStorage<PoiSection> {
- @Override
- public void tick(BooleanSupplier shouldKeepTicking) {
- // Paper start - async chunk io
-- while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean()) {
-+ while (!this.dirty.isEmpty() && shouldKeepTicking.getAsBoolean() && !this.world.noSave()) { // Paper - unload POI data - don't write to disk if saving is disabled
- ChunkPos chunkcoordintpair = SectionPos.of(this.dirty.firstLong()).chunk();
-
- net.minecraft.nbt.CompoundTag data;
-@@ -227,19 +357,24 @@ public class PoiManager extends SectionStorage<PoiSection> {
- com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world,
- chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY);
- }
-+ // Paper start - unload POI data
-+ if (!this.world.noSave()) { // don't write to disk if saving is disabled
-+ this.pollUnloads(shouldKeepTicking);
-+ }
-+ // Paper end - unload POI data
- // Paper end
-- this.distanceTracker.runAllUpdates();
-+ this.villageDistanceTracker.propagateUpdates(); // Paper - replace distance tracking until
- }
-
- @Override
- protected void setDirty(long pos) {
- super.setDirty(pos);
-- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false);
-+ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util
- }
-
- @Override
- protected void onSectionLoad(long pos) {
-- this.distanceTracker.update(pos, this.distanceTracker.getLevelFromSource(pos), false);
-+ this.updateDistanceTracking(pos); // Paper - move to new distance tracking util
- }
-
- public void checkConsistencyWithBlocks(ChunkPos chunkPos, LevelChunkSection chunkSection) {
-@@ -297,7 +432,7 @@ public class PoiManager extends SectionStorage<PoiSection> {
-
- @Override
- protected int getLevelFromSource(long id) {
-- return PoiManager.this.isVillageCenter(id) ? 0 : 7;
-+ return PoiManager.this.isVillageCenter(id) ? 0 : 7; // Paper - unload poi data - diff on change, this specifies the source level to use for distance tracking
- }
-
- @Override
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-index 10e8d1e36639cca21aa451e81cdab90ba9e9a496..954819db8ada38ef2c832151be8a96492e76390a 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java
-@@ -58,6 +58,40 @@ public class SectionStorage<R> extends RegionFileStorage implements AutoCloseabl
- // Paper - remove mojang I/O thread
- }
-
-+ // Paper start - actually unload POI data
-+ public void unloadData(long coordinate) {
-+ ChunkPos chunkPos = new ChunkPos(coordinate);
-+ this.flush(chunkPos);
-+
-+ Long2ObjectMap<Optional<R>> data = this.storage;
-+ int before = data.size();
-+
-+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
-+ data.remove(SectionPos.asLong(chunkPos.x, section, chunkPos.z));
-+ }
-+
-+ if (before != data.size()) {
-+ this.onUnload(coordinate);
-+ }
-+ }
-+
-+ protected void onUnload(long coordinate) {}
-+
-+ public boolean isEmpty(long coordinate) {
-+ Long2ObjectMap<Optional<R>> data = this.storage;
-+ int x = net.minecraft.server.MCUtil.getCoordinateX(coordinate);
-+ int z = net.minecraft.server.MCUtil.getCoordinateZ(coordinate);
-+ for (int section = this.levelHeightAccessor.getMinSection(); section < this.levelHeightAccessor.getMaxSection(); ++section) {
-+ Optional<R> optional = data.get(SectionPos.asLong(x, section, z));
-+ if (optional != null && optional.orElse(null) != null) {
-+ return false;
-+ }
-+ }
-+
-+ return true;
-+ }
-+ // Paper end - actually unload POI data
-+
- protected void tick(BooleanSupplier shouldKeepTicking) {
- while(this.hasWork() && shouldKeepTicking.getAsBoolean()) {
- ChunkPos chunkPos = SectionPos.of(this.dirty.firstLong()).chunk();
-@@ -175,6 +209,7 @@ public class SectionStorage<R> extends RegionFileStorage implements AutoCloseabl
- });
- }
- }
-+ if (this instanceof net.minecraft.world.entity.ai.village.poi.PoiManager) { ((net.minecraft.world.entity.ai.village.poi.PoiManager)this).queueUnload(pos.longKey, net.minecraft.server.MinecraftServer.currentTickLong + 1); } // Paper - unload POI data
-
- }
-
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0845-Replace-ticket-level-propagator.patch b/patches/removed/1.19.2-legacy-chunksystem/0845-Replace-ticket-level-propagator.patch
deleted file mode 100644
index 8d248b38bc..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0845-Replace-ticket-level-propagator.patch
+++ /dev/null
@@ -1,258 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sun, 21 Mar 2021 16:25:42 -0700
-Subject: [PATCH] Replace ticket level propagator
-
-Mojang's propagator is slow, and this isn't surprising
-given it's built on the same utilities the vanilla light engine
-is built on. The simple propagator I wrote is approximately 4x
-faster when simulating player movement. For a long time timing
-reports have shown this function take up significant tick, (
-approx 10% or more), and async sampling data shows the level
-propagation alone takes up a significant amount. So this
-should help with that. A big side effect is that mid-tick
-will be more effective, since more time will be allocated
-to actually processing chunk tasks vs the ticket level updates.
-
-diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index 06e4d3a02e0d1326b7029157856476db4ef3575e..f581a9f79b2357118d912a15344ff94df3b0c50e 100644
---- a/src/main/java/net/minecraft/server/level/DistanceManager.java
-+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -38,6 +38,7 @@ import net.minecraft.world.level.chunk.ChunkStatus;
- import net.minecraft.world.level.chunk.LevelChunk;
- import org.slf4j.Logger;
-
-+import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; // Paper
- public abstract class DistanceManager {
-
- static final Logger LOGGER = LogUtils.getLogger();
-@@ -48,7 +49,7 @@ public abstract class DistanceManager {
- private static final int BLOCK_TICKING_LEVEL_THRESHOLD = 33;
- final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap();
- public final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
-- private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker();
-+ //private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Paper - replace ticket level propagator
- public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
- private final TickingTracker tickingTicketsTracker = new TickingTracker();
- private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33);
-@@ -83,6 +84,46 @@ public abstract class DistanceManager {
- this.chunkMap = chunkMap; // Paper
- }
-
-+ // Paper start - replace ticket level propagator
-+ protected final Long2IntLinkedOpenHashMap ticketLevelUpdates = new Long2IntLinkedOpenHashMap() {
-+ @Override
-+ protected void rehash(int newN) {
-+ // no downsizing allowed
-+ if (newN < this.n) {
-+ return;
-+ }
-+ super.rehash(newN);
-+ }
-+ };
-+ protected final io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D ticketLevelPropagator = new io.papermc.paper.util.misc.Delayed8WayDistancePropagator2D(
-+ (long coordinate, byte oldLevel, byte newLevel) -> {
-+ DistanceManager.this.ticketLevelUpdates.putAndMoveToLast(coordinate, convertBetweenTicketLevels(newLevel));
-+ }
-+ );
-+ // function for converting between ticket levels and propagator levels and vice versa
-+ // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects
-+ // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator
-+ // and the levels we get out of the propagator
-+
-+ // this maps so that GOLDEN_TICKET + 1 will be 0 in the propagator, GOLDEN_TICKET will be 1, and so on
-+ // we need GOLDEN_TICKET+1 as 0 because anything >= GOLDEN_TICKET+1 should be unloaded
-+ public static int convertBetweenTicketLevels(final int level) {
-+ return ChunkMap.MAX_CHUNK_DISTANCE - level + 1;
-+ }
-+
-+ protected final int getPropagatedTicketLevel(final long coordinate) {
-+ return convertBetweenTicketLevels(this.ticketLevelPropagator.getLevel(coordinate));
-+ }
-+
-+ protected final void updateTicketLevel(final long coordinate, final int ticketLevel) {
-+ if (ticketLevel > ChunkMap.MAX_CHUNK_DISTANCE) {
-+ this.ticketLevelPropagator.removeSource(coordinate);
-+ } else {
-+ this.ticketLevelPropagator.setSource(coordinate, convertBetweenTicketLevels(ticketLevel));
-+ }
-+ }
-+ // Paper end - replace ticket level propagator
-+
- protected void purgeStaleTickets() {
- ++this.ticketTickCounter;
- ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
-@@ -117,7 +158,7 @@ public abstract class DistanceManager {
- }
-
- if (flag) {
-- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false);
-+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(entry.getValue())); // Paper - replace ticket level propagator
- }
-
- if (((SortedArraySet) entry.getValue()).isEmpty()) {
-@@ -140,61 +181,94 @@ public abstract class DistanceManager {
- @Nullable
- protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k);
-
-+ protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator
- public boolean runAllUpdates(ChunkMap chunkStorage) {
- //this.f.a(); // Paper - no longer used
- this.tickingTicketsTracker.runAllUpdates();
- org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
- this.playerTicketManager.runAllUpdates();
-- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
-- boolean flag = i != 0;
-+ boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator
-
- if (flag) {
- ;
- }
-
-- // Paper start
-- if (!this.pendingChunkUpdates.isEmpty()) {
-- this.pollingPendingChunkUpdates = true; try { // Paper - Chunk priority
-- while(!this.pendingChunkUpdates.isEmpty()) {
-- ChunkHolder remove = this.pendingChunkUpdates.remove();
-- remove.isUpdateQueued = false;
-- remove.updateFutures(chunkStorage, this.mainThreadExecutor);
-- }
-- } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority
-- // Paper end
-- return true;
-- } else {
-- if (!this.ticketsToRelease.isEmpty()) {
-- LongIterator longiterator = this.ticketsToRelease.iterator();
-+ // Paper start - replace level propagator
-+ ticket_update_loop:
-+ while (!this.ticketLevelUpdates.isEmpty()) {
-+ flag = true;
-
-- while (longiterator.hasNext()) {
-- long j = longiterator.nextLong();
-+ boolean oldPolling = this.pollingPendingChunkUpdates;
-+ this.pollingPendingChunkUpdates = true;
-+ try {
-+ for (java.util.Iterator<Long2IntMap.Entry> iterator = this.ticketLevelUpdates.long2IntEntrySet().fastIterator(); iterator.hasNext();) {
-+ Long2IntMap.Entry entry = iterator.next();
-+ long key = entry.getLongKey();
-+ int newLevel = entry.getIntValue();
-+ ChunkHolder chunk = this.getChunk(key);
-+
-+ if (chunk == null && newLevel > ChunkMap.MAX_CHUNK_DISTANCE) {
-+ // not loaded and it shouldn't be loaded!
-+ continue;
-+ }
-
-- if (this.getTickets(j).stream().anyMatch((ticket) -> {
-- return ticket.getType() == TicketType.PLAYER;
-- })) {
-- ChunkHolder playerchunk = chunkStorage.getUpdatingChunkIfPresent(j);
-+ int currentLevel = chunk == null ? ChunkMap.MAX_CHUNK_DISTANCE + 1 : chunk.getTicketLevel();
-
-- if (playerchunk == null) {
-- throw new IllegalStateException();
-+ if (currentLevel == newLevel) {
-+ // nothing to do
-+ continue;
-+ }
-+
-+ this.updateChunkScheduling(key, newLevel, chunk, currentLevel);
-+ }
-+
-+ long recursiveCheck = ++this.ticketLevelUpdateCount;
-+ while (!this.ticketLevelUpdates.isEmpty()) {
-+ long key = this.ticketLevelUpdates.firstLongKey();
-+ int newLevel = this.ticketLevelUpdates.removeFirstInt();
-+ ChunkHolder chunk = this.getChunk(key);
-+
-+ if (chunk == null) {
-+ if (newLevel <= ChunkMap.MAX_CHUNK_DISTANCE) {
-+ throw new IllegalStateException("Expected chunk holder to be created");
- }
-+ // not loaded and it shouldn't be loaded!
-+ continue;
-+ }
-
-- CompletableFuture<Either<LevelChunk, ChunkHolder.ChunkLoadingFailure>> completablefuture = playerchunk.getEntityTickingChunkFuture();
-+ int currentLevel = chunk.oldTicketLevel;
-
-- completablefuture.thenAccept((either) -> {
-- this.mainThreadExecutor.execute(() -> {
-- this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> {
-- }, j, false));
-- });
-- });
-+ if (currentLevel == newLevel) {
-+ // nothing to do
-+ continue;
-+ }
-+
-+ chunk.updateFutures(chunkStorage, this.mainThreadExecutor);
-+ if (recursiveCheck != this.ticketLevelUpdateCount) {
-+ // back to the start, we must create player chunks and update the ticket level fields before
-+ // processing the actual level updates
-+ continue ticket_update_loop;
- }
- }
-
-- this.ticketsToRelease.clear();
-- }
-+ for (;;) {
-+ if (recursiveCheck != this.ticketLevelUpdateCount) {
-+ continue ticket_update_loop;
-+ }
-+ ChunkHolder pendingUpdate = this.pendingChunkUpdates.poll();
-+ if (pendingUpdate == null) {
-+ break;
-+ }
-
-- return flag;
-+ pendingUpdate.updateFutures(chunkStorage, this.mainThreadExecutor);
-+ }
-+ } finally {
-+ this.pollingPendingChunkUpdates = oldPolling;
-+ }
- }
-+
-+ return flag;
-+ // Paper end - replace level propagator
- }
- boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority
-
-@@ -206,7 +280,7 @@ public abstract class DistanceManager {
-
- ticket1.setCreatedTick(this.ticketTickCounter);
- if (ticket.getTicketLevel() < j) {
-- this.ticketTracker.update(i, ticket.getTicketLevel(), true);
-+ this.updateTicketLevel(i, ticket.getTicketLevel()); // Paper - replace ticket level propagator
- }
-
- return ticket == ticket1; // CraftBukkit
-@@ -250,7 +324,7 @@ public abstract class DistanceManager {
- // Paper start - Chunk priority
- int newLevel = getTicketLevelAt(arraysetsorted);
- if (newLevel > oldLevel) {
-- this.ticketTracker.update(i, newLevel, false);
-+ this.updateTicketLevel(i, newLevel); // Paper - replace ticket level propagator
- }
- // Paper end
- return removed; // CraftBukkit
-@@ -564,7 +638,7 @@ public abstract class DistanceManager {
- }
-
- if (flag) {
-- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false);
-+ this.updateTicketLevel(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue())); // Paper - replace ticket level propagator
- }
-
- if (((SortedArraySet) entry.getValue()).isEmpty()) {
-@@ -587,7 +661,7 @@ public abstract class DistanceManager {
- SortedArraySet<Ticket<?>> tickets = entry.getValue();
- if (tickets.remove(target)) {
- // copied from removeTicket
-- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false);
-+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(tickets)); // Paper - replace ticket level propagator
-
- // can't use entry after it's removed
- if (tickets.isEmpty()) {
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0853-Replace-player-chunk-loader-system.patch b/patches/removed/1.19.2-legacy-chunksystem/0853-Replace-player-chunk-loader-system.patch
deleted file mode 100644
index a6d7f180d4..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0853-Replace-player-chunk-loader-system.patch
+++ /dev/null
@@ -1,2270 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sun, 24 Jan 2021 20:27:32 -0800
-Subject: [PATCH] Replace player chunk loader system
-
-The old one has undebuggable problems. Rewriting seems
-the most sensible option.
-
-This new player chunk manager will also strictly rate limit
-chunk sends so that netty threads do not get overloaded, whether
-it be from the anti-xray logic or the compression itself.
-
-Chunk loading is also rate limited in the same manner, so this
-will result in a maximum responsiveness for change.
-
-Config:
-```
-chunk-loading:
- min-load-radius: 2
- max-concurrent-sends: 2
- autoconfig-send-distance: true
- target-player-chunk-send-rate: 100.0
- global-max-chunk-send-rate: -1
- enable-frustum-priority: false
- global-max-chunk-load-rate: -1.0
- player-max-concurrent-loads: 25.0
- global-max-concurrent-loads: 500.0
-```
-
-min-load-radius - The radius of chunks around a player that
-are not throttled for loading. The number of chunks
-affected is actually the configured value plus one as this
-config controls the chunks the client will be able to render.
-
-max-concurrent-sends - The maximum number of chunks that
-can be queued to send at any given time. Low values
-are generally going to solve server-sided networking
-bottlenecks like anti-xray and chunk compression. Client
-side networking is unlikely to be helped (i.e this wont help
-people running off McDonald's wifi).
-
-autoconfig-send-distance - Whether to try to use the client's
-view distance for the send view distance in the server. In the
-case that no plugin has explicitly set the send distance and
-the client view distance is less-than the server's send distance,
-the client's view distance will be used. This will not affect
-tick view distance or no-tick view distance.
-
-target-player-chunk-send-rate - The maximum chunk send rate
-an individual player will have. -1 means no limit
-
-global-max-chunk-send-rate - The maximum chunk send rate for
-the whole server. -1 means no limit
-
-enable-frustum-priority - Whether chunks in front of a player
-are prioritised to load/send first. Disabled by default
-because the client can bug out due to the out of order
-chunk sending.
-
-global-max-chunk-load-rate - The maximum chunk load rate
-for the whole server. -1 means no limit
-
-player-max-concurrent-loads and global-max-concurrent-loads
-The maximum number of concurrent loads for the server is
-determined by the number of players on the server multiplied by the
-`player-max-concurrent-loads`. It is then limited to
-whatever `global-max-concurrent-loads` is configured to.
-
-diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
-index 78280fb3bcd8d792a58ece6d735e0824ea4be536..06bff37e4c1fddd3be6343049a66787c63fb420c 100644
---- a/src/main/java/co/aikar/timings/TimingsExport.java
-+++ b/src/main/java/co/aikar/timings/TimingsExport.java
-@@ -162,7 +162,11 @@ public class TimingsExport extends Thread {
- pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> {
- return pair(rule, world.getWorld().getGameRuleValue(rule));
- })),
-- pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance())
-+ // Paper start - replace chunk loader system
-+ pair("ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()),
-+ pair("no-ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()),
-+ pair("sending-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance())
-+ // Paper end - replace chunk loader system
- ));
- }));
-
-diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
-new file mode 100644
-index 0000000000000000000000000000000000000000..b53402903eb6845df361daf6b05a668608ad7b63
---- /dev/null
-+++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
-@@ -0,0 +1,1128 @@
-+package io.papermc.paper.chunk;
-+
-+import com.destroystokyo.paper.util.misc.PlayerAreaMap;
-+import com.destroystokyo.paper.util.misc.PooledLinkedHashSets;
-+import io.papermc.paper.configuration.GlobalConfiguration;
-+import io.papermc.paper.util.CoordinateUtils;
-+import io.papermc.paper.util.IntervalledCounter;
-+import io.papermc.paper.util.TickThread;
-+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
-+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
-+import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap;
-+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
-+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket;
-+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket;
-+import net.minecraft.network.protocol.game.ClientboundSetSimulationDistancePacket;
-+import net.minecraft.server.MCUtil;
-+import net.minecraft.server.MinecraftServer;
-+import net.minecraft.server.level.*;
-+import net.minecraft.util.Mth;
-+import net.minecraft.world.level.ChunkPos;
-+import net.minecraft.world.level.chunk.LevelChunk;
-+import org.apache.commons.lang3.mutable.MutableObject;
-+import org.bukkit.craftbukkit.entity.CraftPlayer;
-+import org.bukkit.entity.Player;
-+import java.util.ArrayDeque;
-+import java.util.ArrayList;
-+import java.util.List;
-+import java.util.TreeSet;
-+import java.util.concurrent.atomic.AtomicInteger;
-+
-+public final class PlayerChunkLoader {
-+
-+ public static final int MIN_VIEW_DISTANCE = 2;
-+ public static final int MAX_VIEW_DISTANCE = 32;
-+
-+ public static final int TICK_TICKET_LEVEL = 31;
-+ public static final int LOADED_TICKET_LEVEL = 33;
-+
-+ public static int getTickViewDistance(final Player player) {
-+ return getTickViewDistance(((CraftPlayer)player).getHandle());
-+ }
-+
-+ public static int getTickViewDistance(final ServerPlayer player) {
-+ final ServerLevel level = (ServerLevel)player.level;
-+ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
-+ if (data == null) {
-+ return level.chunkSource.chunkMap.playerChunkManager.getTargetTickViewDistance();
-+ }
-+ return data.getTargetTickViewDistance();
-+ }
-+
-+ public static int getLoadViewDistance(final Player player) {
-+ return getLoadViewDistance(((CraftPlayer)player).getHandle());
-+ }
-+
-+ public static int getLoadViewDistance(final ServerPlayer player) {
-+ final ServerLevel level = (ServerLevel)player.level;
-+ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
-+ if (data == null) {
-+ return level.chunkSource.chunkMap.playerChunkManager.getLoadDistance();
-+ }
-+ return data.getLoadDistance();
-+ }
-+
-+ public static int getSendViewDistance(final Player player) {
-+ return getSendViewDistance(((CraftPlayer)player).getHandle());
-+ }
-+
-+ public static int getSendViewDistance(final ServerPlayer player) {
-+ final ServerLevel level = (ServerLevel)player.level;
-+ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
-+ if (data == null) {
-+ return level.chunkSource.chunkMap.playerChunkManager.getTargetSendDistance();
-+ }
-+ return data.getTargetSendViewDistance();
-+ }
-+
-+ protected final ChunkMap chunkMap;
-+ protected final Reference2ObjectLinkedOpenHashMap<ServerPlayer, PlayerLoaderData> playerMap = new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f);
-+ protected final ReferenceLinkedOpenHashSet<PlayerLoaderData> chunkSendQueue = new ReferenceLinkedOpenHashSet<>(512, 0.7f);
-+
-+ protected final TreeSet<PlayerLoaderData> chunkLoadQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> {
-+ if (p1 == p2) {
-+ return 0;
-+ }
-+
-+ final ChunkPriorityHolder holder1 = p1.loadQueue.peekFirst();
-+ final ChunkPriorityHolder holder2 = p2.loadQueue.peekFirst();
-+
-+ final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority);
-+
-+ final int lastLoadTimeCompare = Long.compare(p1.lastChunkLoad, p2.lastChunkLoad);
-+
-+ if ((holder1 == null || holder2 == null || lastLoadTimeCompare == 0 || holder1.priority < 0.0 || holder2.priority < 0.0) && priorityCompare != 0) {
-+ return priorityCompare;
-+ }
-+
-+ if (lastLoadTimeCompare != 0) {
-+ return lastLoadTimeCompare;
-+ }
-+
-+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId());
-+
-+ if (idCompare != 0) {
-+ return idCompare;
-+ }
-+
-+ // last resort
-+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2));
-+ });
-+
-+ protected final TreeSet<PlayerLoaderData> chunkSendWaitQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> {
-+ if (p1 == p2) {
-+ return 0;
-+ }
-+
-+ final int timeCompare = Long.compare(p1.nextChunkSendTarget, p2.nextChunkSendTarget);
-+ if (timeCompare != 0) {
-+ return timeCompare;
-+ }
-+
-+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId());
-+
-+ if (idCompare != 0) {
-+ return idCompare;
-+ }
-+
-+ // last resort
-+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2));
-+ });
-+
-+
-+ // no throttling is applied below this VD for loading
-+
-+ /**
-+ * The chunks to be sent to players, provided they're send-ready. Send-ready means the chunk and its 1 radius neighbours are loaded.
-+ */
-+ public final PlayerAreaMap broadcastMap;
-+
-+ /**
-+ * The chunks to be brought up to send-ready status. Send-ready means the chunk and its 1 radius neighbours are loaded.
-+ */
-+ public final PlayerAreaMap loadMap;
-+
-+ /**
-+ * Areamap used only to remove tickets for send-ready chunks. View distance is always + 1 of load view distance. Thus,
-+ * this map is always representing the chunks we are actually going to load.
-+ */
-+ public final PlayerAreaMap loadTicketCleanup;
-+
-+ /**
-+ * The chunks to brought to ticking level. Each chunk must have 2 radius neighbours loaded before this can happen.
-+ */
-+ public final PlayerAreaMap tickMap;
-+
-+ /**
-+ * -1 if defaulting to [load distance], else always in [2, load distance]
-+ */
-+ protected int rawSendDistance = -1;
-+
-+ /**
-+ * -1 if defaulting to [tick view distance + 1], else always in [tick view distance + 1, 32 + 1]
-+ */
-+ protected int rawLoadDistance = -1;
-+
-+ /**
-+ * Never -1, always in [2, 32]
-+ */
-+ protected int rawTickDistance = -1;
-+
-+ // methods to bridge for API
-+
-+ public int getTargetTickViewDistance() {
-+ return this.getTickDistance();
-+ }
-+
-+ public void setTargetTickViewDistance(final int distance) {
-+ this.setTickDistance(distance);
-+ }
-+
-+ public int getTargetNoTickViewDistance() {
-+ return this.getLoadDistance() - 1;
-+ }
-+
-+ public void setTargetNoTickViewDistance(final int distance) {
-+ this.setLoadDistance(distance == -1 ? -1 : distance + 1);
-+ }
-+
-+ public int getTargetSendDistance() {
-+ return this.rawSendDistance == -1 ? this.getLoadDistance() : this.rawSendDistance;
-+ }
-+
-+ public void setTargetSendDistance(final int distance) {
-+ this.setSendDistance(distance);
-+ }
-+
-+ // internal methods
-+
-+ public int getSendDistance() {
-+ final int loadDistance = this.getLoadDistance();
-+ return this.rawSendDistance == -1 ? loadDistance : Math.min(this.rawSendDistance, loadDistance);
-+ }
-+
-+ public void setSendDistance(final int distance) {
-+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) {
-+ throw new IllegalArgumentException("Send distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + ", or -1, got: " + distance);
-+ }
-+ this.rawSendDistance = distance;
-+ }
-+
-+ public int getLoadDistance() {
-+ final int tickDistance = this.getTickDistance();
-+ return this.rawLoadDistance == -1 ? tickDistance + 1 : Math.max(tickDistance + 1, this.rawLoadDistance);
-+ }
-+
-+ public void setLoadDistance(final int distance) {
-+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) {
-+ throw new IllegalArgumentException("Load distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + ", or -1, got: " + distance);
-+ }
-+ this.rawLoadDistance = distance;
-+ }
-+
-+ public int getTickDistance() {
-+ return this.rawTickDistance;
-+ }
-+
-+ public void setTickDistance(final int distance) {
-+ if (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE) {
-+ throw new IllegalArgumentException("View distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + ", got: " + distance);
-+ }
-+ this.rawTickDistance = distance;
-+ }
-+
-+ /*
-+ Players have 3 different types of view distance:
-+ 1. Sending view distance
-+ 2. Loading view distance
-+ 3. Ticking view distance
-+
-+ But for configuration purposes (and API) there are:
-+ 1. No-tick view distance
-+ 2. Tick view distance
-+ 3. Broadcast view distance
-+
-+ These aren't always the same as the types we represent internally.
-+
-+ Loading view distance is always max(no-tick + 1, tick + 1)
-+ - no-tick has 1 added because clients need an extra radius to render chunks
-+ - tick has 1 added because it needs an extra radius of chunks to load before they can be marked ticking
-+
-+ Loading view distance is defined as the radius of chunks that will be brought to send-ready status, which means
-+ it loads chunks in radius load-view-distance + 1.
-+
-+ The maximum value for send view distance is the load view distance. API can set it lower.
-+ */
-+
-+ public PlayerChunkLoader(final ChunkMap chunkMap, final PooledLinkedHashSets<ServerPlayer> pooledHashSets) {
-+ this.chunkMap = chunkMap;
-+ this.broadcastMap = new PlayerAreaMap(pooledHashSets,
-+ null,
-+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
-+ PlayerChunkLoader.this.onChunkLeave(player, rangeX, rangeZ);
-+ });
-+ this.loadMap = new PlayerAreaMap(pooledHashSets,
-+ null,
-+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
-+ if (newState != null) {
-+ return;
-+ }
-+ PlayerChunkLoader.this.isTargetedForPlayerLoad.remove(CoordinateUtils.getChunkKey(rangeX, rangeZ));
-+ });
-+ this.loadTicketCleanup = new PlayerAreaMap(pooledHashSets,
-+ null,
-+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
-+ if (newState != null) {
-+ return;
-+ }
-+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
-+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos);
-+ if (PlayerChunkLoader.this.chunkTicketTracker.remove(chunkPos.toLong())) {
-+ --PlayerChunkLoader.this.concurrentChunkLoads;
-+ }
-+ });
-+ this.tickMap = new PlayerAreaMap(pooledHashSets,
-+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
-+ if (newState.size() != 1) {
-+ return;
-+ }
-+ LevelChunk chunk = PlayerChunkLoader.this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ);
-+ if (chunk == null || !chunk.areNeighboursLoaded(2)) {
-+ return;
-+ }
-+
-+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
-+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos);
-+ },
-+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
-+ if (newState != null) {
-+ return;
-+ }
-+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
-+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos);
-+ });
-+ }
-+
-+ protected final LongOpenHashSet isTargetedForPlayerLoad = new LongOpenHashSet();
-+ protected final LongOpenHashSet chunkTicketTracker = new LongOpenHashSet();
-+
-+ public boolean isChunkNearPlayers(final int chunkX, final int chunkZ) {
-+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ);
-+
-+ return playersInSendRange != null;
-+ }
-+
-+ public void onChunkPostProcessing(final int chunkX, final int chunkZ) {
-+ this.onChunkSendReady(chunkX, chunkZ);
-+ }
-+
-+ private boolean chunkNeedsPostProcessing(final int chunkX, final int chunkZ) {
-+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ);
-+ final ChunkHolder chunk = this.chunkMap.getVisibleChunkIfPresent(key);
-+
-+ if (chunk == null) {
-+ return false;
-+ }
-+
-+ final LevelChunk levelChunk = chunk.getSendingChunk();
-+
-+ return levelChunk != null && !levelChunk.isPostProcessingDone;
-+ }
-+
-+ // rets whether the chunk is at a loaded stage that is ready to be sent to players
-+ public boolean isChunkPlayerLoaded(final int chunkX, final int chunkZ) {
-+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ);
-+ final ChunkHolder chunk = this.chunkMap.getVisibleChunkIfPresent(key);
-+
-+ if (chunk == null) {
-+ return false;
-+ }
-+
-+ final LevelChunk levelChunk = chunk.getSendingChunk();
-+
-+ return levelChunk != null && levelChunk.isPostProcessingDone && this.isTargetedForPlayerLoad.contains(key);
-+ }
-+
-+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ, final boolean borderOnly) {
-+ return borderOnly ? this.isChunkSentBorderOnly(player, chunkX, chunkZ) : this.isChunkSent(player, chunkX, chunkZ);
-+ }
-+
-+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ) {
-+ final PlayerLoaderData data = this.playerMap.get(player);
-+ if (data == null) {
-+ return false;
-+ }
-+
-+ return data.hasSentChunk(chunkX, chunkZ);
-+ }
-+
-+ public boolean isChunkSentBorderOnly(final ServerPlayer player, final int chunkX, final int chunkZ) {
-+ final PlayerLoaderData data = this.playerMap.get(player);
-+ if (data == null) {
-+ return false;
-+ }
-+
-+ final boolean center = data.hasSentChunk(chunkX, chunkZ);
-+ if (!center) {
-+ return false;
-+ }
-+
-+ return !(data.hasSentChunk(chunkX - 1, chunkZ) && data.hasSentChunk(chunkX + 1, chunkZ) &&
-+ data.hasSentChunk(chunkX, chunkZ - 1) && data.hasSentChunk(chunkX, chunkZ + 1));
-+ }
-+
-+ protected int getMaxConcurrentChunkSends() {
-+ return GlobalConfiguration.get().chunkLoading.maxConcurrentSends;
-+ }
-+
-+ protected int getMaxChunkLoads() {
-+ double config = GlobalConfiguration.get().chunkLoading.playerMaxConcurrentLoads;
-+ double max = GlobalConfiguration.get().chunkLoading.globalMaxConcurrentLoads;
-+ return (int)Math.ceil(Math.min(config * MinecraftServer.getServer().getPlayerCount(), max <= 1.0 ? Double.MAX_VALUE : max));
-+ }
-+
-+ protected long getTargetSendPerPlayerAddend() {
-+ return GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.targetPlayerChunkSendRate);
-+ }
-+
-+ protected long getMaxSendAddend() {
-+ return GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / GlobalConfiguration.get().chunkLoading.globalMaxChunkSendRate);
-+ }
-+
-+ public void onChunkPlayerTickReady(final int chunkX, final int chunkZ) {
-+ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ);
-+ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos);
-+ }
-+
-+ public void onChunkSendReady(final int chunkX, final int chunkZ) {
-+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ);
-+
-+ if (playersInSendRange == null) {
-+ return;
-+ }
-+
-+ final Object[] rawData = playersInSendRange.getBackingSet();
-+ for (int i = 0, len = rawData.length; i < len; ++i) {
-+ final Object raw = rawData[i];
-+
-+ if (!(raw instanceof ServerPlayer)) {
-+ continue;
-+ }
-+ this.onChunkSendReady((ServerPlayer)raw, chunkX, chunkZ);
-+ }
-+ }
-+
-+ public void onChunkSendReady(final ServerPlayer player, final int chunkX, final int chunkZ) {
-+ final PlayerLoaderData data = this.playerMap.get(player);
-+
-+ if (data == null) {
-+ return;
-+ }
-+
-+ if (data.hasSentChunk(chunkX, chunkZ) || !this.isChunkPlayerLoaded(chunkX, chunkZ)) {
-+ // if we don't have player tickets, then the load logic will pick this up and queue to send
-+ return;
-+ }
-+
-+ if (!data.chunksToBeSent.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
-+ // don't queue to send, we don't want the chunk
-+ return;
-+ }
-+
-+ final long playerPos = this.broadcastMap.getLastCoordinate(player);
-+ final int playerChunkX = CoordinateUtils.getChunkX(playerPos);
-+ final int playerChunkZ = CoordinateUtils.getChunkZ(playerPos);
-+ final int manhattanDistance = Math.abs(playerChunkX - chunkX) + Math.abs(playerChunkZ - chunkZ);
-+
-+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, 0.0);
-+ data.sendQueue.add(holder);
-+ }
-+
-+ public void onChunkLoad(final int chunkX, final int chunkZ) {
-+ if (this.chunkTicketTracker.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
-+ --this.concurrentChunkLoads;
-+ }
-+ }
-+
-+ public void onChunkLeave(final ServerPlayer player, final int chunkX, final int chunkZ) {
-+ final PlayerLoaderData data = this.playerMap.get(player);
-+
-+ if (data == null) {
-+ return;
-+ }
-+
-+ data.unloadChunk(chunkX, chunkZ);
-+ }
-+
-+ public void addPlayer(final ServerPlayer player) {
-+ TickThread.ensureTickThread("Cannot add player async");
-+ if (!player.isRealPlayer) {
-+ return;
-+ }
-+ final PlayerLoaderData data = new PlayerLoaderData(player, this);
-+ if (this.playerMap.putIfAbsent(player, data) == null) {
-+ data.update();
-+ }
-+ }
-+
-+ public void removePlayer(final ServerPlayer player) {
-+ TickThread.ensureTickThread("Cannot remove player async");
-+ if (!player.isRealPlayer) {
-+ return;
-+ }
-+
-+ final PlayerLoaderData loaderData = this.playerMap.remove(player);
-+ if (loaderData == null) {
-+ return;
-+ }
-+ loaderData.remove();
-+ this.chunkLoadQueue.remove(loaderData);
-+ this.chunkSendQueue.remove(loaderData);
-+ this.chunkSendWaitQueue.remove(loaderData);
-+ synchronized (this.sendingChunkCounts) {
-+ final int count = this.sendingChunkCounts.removeInt(loaderData);
-+ if (count != 0) {
-+ concurrentChunkSends.getAndAdd(-count);
-+ }
-+ }
-+ }
-+
-+ public void updatePlayer(final ServerPlayer player) {
-+ TickThread.ensureTickThread("Cannot update player async");
-+ if (!player.isRealPlayer) {
-+ return;
-+ }
-+ final PlayerLoaderData loaderData = this.playerMap.get(player);
-+ if (loaderData != null) {
-+ loaderData.update();
-+ }
-+ }
-+
-+ public PlayerLoaderData getData(final ServerPlayer player) {
-+ return this.playerMap.get(player);
-+ }
-+
-+ public void tick() {
-+ TickThread.ensureTickThread("Cannot tick async");
-+ for (final PlayerLoaderData data : this.playerMap.values()) {
-+ data.update();
-+ }
-+ this.tickMidTick();
-+ }
-+
-+ protected static final AtomicInteger concurrentChunkSends = new AtomicInteger();
-+ protected final Reference2IntOpenHashMap<PlayerLoaderData> sendingChunkCounts = new Reference2IntOpenHashMap<>();
-+ private static long nextChunkSend;
-+ private void trySendChunks() {
-+ final long time = System.nanoTime();
-+ if (time < nextChunkSend) {
-+ return;
-+ }
-+ // drain entries from wait queue
-+ while (!this.chunkSendWaitQueue.isEmpty()) {
-+ final PlayerLoaderData data = this.chunkSendWaitQueue.first();
-+
-+ if (data.nextChunkSendTarget > time) {
-+ break;
-+ }
-+
-+ this.chunkSendWaitQueue.pollFirst();
-+
-+ this.chunkSendQueue.add(data);
-+ }
-+
-+ if (this.chunkSendQueue.isEmpty()) {
-+ return;
-+ }
-+
-+ final int maxSends = this.getMaxConcurrentChunkSends();
-+ final long nextPlayerDeadline = this.getTargetSendPerPlayerAddend() + time;
-+ for (;;) {
-+ if (this.chunkSendQueue.isEmpty()) {
-+ break;
-+ }
-+ final int currSends = concurrentChunkSends.get();
-+ if (currSends >= maxSends) {
-+ break;
-+ }
-+
-+ if (!concurrentChunkSends.compareAndSet(currSends, currSends + 1)) {
-+ continue;
-+ }
-+
-+ // send chunk
-+
-+ final PlayerLoaderData data = this.chunkSendQueue.removeFirst();
-+
-+ final ChunkPriorityHolder queuedSend = data.sendQueue.pollFirst();
-+ if (queuedSend == null) {
-+ concurrentChunkSends.getAndDecrement(); // we never sent, so decrease
-+ // stop iterating over players who have nothing to send
-+ if (this.chunkSendQueue.isEmpty()) {
-+ // nothing left
-+ break;
-+ }
-+ continue;
-+ }
-+
-+ if (!this.isChunkPlayerLoaded(queuedSend.chunkX, queuedSend.chunkZ)) {
-+ throw new IllegalStateException();
-+ }
-+
-+ data.nextChunkSendTarget = nextPlayerDeadline;
-+ this.chunkSendWaitQueue.add(data);
-+
-+ synchronized (this.sendingChunkCounts) {
-+ this.sendingChunkCounts.addTo(data, 1);
-+ }
-+
-+ data.sendChunk(queuedSend.chunkX, queuedSend.chunkZ, () -> {
-+ synchronized (this.sendingChunkCounts) {
-+ final int count = this.sendingChunkCounts.getInt(data);
-+ if (count == 0) {
-+ // disconnected, so we don't need to decrement: it will be decremented for us
-+ return;
-+ }
-+ if (count == 1) {
-+ this.sendingChunkCounts.removeInt(data);
-+ } else {
-+ this.sendingChunkCounts.put(data, count - 1);
-+ }
-+ }
-+
-+ concurrentChunkSends.getAndDecrement();
-+ });
-+
-+ nextChunkSend = this.getMaxSendAddend() + time;
-+ if (time < nextChunkSend) {
-+ break;
-+ }
-+ }
-+ }
-+
-+ protected int concurrentChunkLoads;
-+ // this interval prevents bursting a lot of chunk loads
-+ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_SHORT = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms
-+ // this interval ensures the rate is kept between ticks correctly
-+ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_LONG = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms
-+ private void tryLoadChunks() {
-+ if (this.chunkLoadQueue.isEmpty()) {
-+ return;
-+ }
-+
-+ final int maxLoads = this.getMaxChunkLoads();
-+ final long time = System.nanoTime();
-+ boolean updatedCounters = false;
-+ for (;;) {
-+ final PlayerLoaderData data = this.chunkLoadQueue.pollFirst();
-+
-+ data.lastChunkLoad = time;
-+
-+ final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst();
-+ if (queuedLoad == null) {
-+ if (this.chunkLoadQueue.isEmpty()) {
-+ break;
-+ }
-+ continue;
-+ }
-+
-+ if (!updatedCounters) {
-+ updatedCounters = true;
-+ TICKET_ADDITION_COUNTER_SHORT.updateCurrentTime(time);
-+ TICKET_ADDITION_COUNTER_LONG.updateCurrentTime(time);
-+ data.ticketAdditionCounterShort.updateCurrentTime(time);
-+ data.ticketAdditionCounterLong.updateCurrentTime(time);
-+ }
-+
-+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) {
-+ // already loaded!
-+ data.loadQueue.pollFirst(); // already loaded so we just skip
-+ this.chunkLoadQueue.add(data);
-+
-+ // ensure the chunk is queued to send
-+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ);
-+ continue;
-+ }
-+
-+ final long chunkKey = CoordinateUtils.getChunkKey(queuedLoad.chunkX, queuedLoad.chunkZ);
-+
-+ final double priority = queuedLoad.priority;
-+ // while we do need to rate limit chunk loads, the logic for sending chunks requires that tickets are present.
-+ // when chunks are loaded (i.e spawn) but do not have this player's tickets, they have to wait behind the
-+ // load queue. To avoid this problem, we check early here if tickets are required to load the chunk - if they
-+ // aren't required, it bypasses the limiter system.
-+ boolean unloadedTargetChunk = false;
-+ unloaded_check:
-+ for (int dz = -1; dz <= 1; ++dz) {
-+ for (int dx = -1; dx <= 1; ++dx) {
-+ final int offX = queuedLoad.chunkX + dx;
-+ final int offZ = queuedLoad.chunkZ + dz;
-+ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) == null) {
-+ unloadedTargetChunk = true;
-+ break unloaded_check;
-+ }
-+ }
-+ }
-+ if (unloadedTargetChunk && priority >= 0.0) {
-+ // priority >= 0.0 implies rate limited chunks
-+
-+ final int currentChunkLoads = this.concurrentChunkLoads;
-+ if (currentChunkLoads >= maxLoads || (GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= GlobalConfiguration.get().chunkLoading.globalMaxChunkLoadRate))
-+ || (GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate > 0.0 && (data.ticketAdditionCounterShort.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate || data.ticketAdditionCounterLong.getRate() >= GlobalConfiguration.get().chunkLoading.playerMaxChunkLoadRate))) {
-+ // don't poll, we didn't load it
-+ this.chunkLoadQueue.add(data);
-+ break;
-+ }
-+ }
-+
-+ // can only poll after we decide to load
-+ data.loadQueue.pollFirst();
-+
-+ // now that we've polled we can re-add to load queue
-+ this.chunkLoadQueue.add(data);
-+
-+ // add necessary tickets to load chunk up to send-ready
-+ for (int dz = -1; dz <= 1; ++dz) {
-+ for (int dx = -1; dx <= 1; ++dx) {
-+ final int offX = queuedLoad.chunkX + dx;
-+ final int offZ = queuedLoad.chunkZ + dz;
-+ final ChunkPos chunkPos = new ChunkPos(offX, offZ);
-+
-+ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos);
-+ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) != null) {
-+ continue;
-+ }
-+
-+ if (priority > 0.0 && this.chunkTicketTracker.add(CoordinateUtils.getChunkKey(offX, offZ))) {
-+ // won't reach here if unloadedTargetChunk is false
-+ ++this.concurrentChunkLoads;
-+ TICKET_ADDITION_COUNTER_SHORT.addTime(time);
-+ TICKET_ADDITION_COUNTER_LONG.addTime(time);
-+ data.ticketAdditionCounterShort.addTime(time);
-+ data.ticketAdditionCounterLong.addTime(time);
-+ }
-+ }
-+ }
-+
-+ // mark that we've added tickets here
-+ this.isTargetedForPlayerLoad.add(chunkKey);
-+
-+ // it's possible all we needed was the player tickets to queue up the send.
-+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) {
-+ // yup, all we needed.
-+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ);
-+ } else if (this.chunkNeedsPostProcessing(queuedLoad.chunkX, queuedLoad.chunkZ)) {
-+ // requires post processing
-+ this.chunkMap.mainThreadExecutor.execute(() -> {
-+ final long key = CoordinateUtils.getChunkKey(queuedLoad.chunkX, queuedLoad.chunkZ);
-+ final ChunkHolder holder = PlayerChunkLoader.this.chunkMap.getVisibleChunkIfPresent(key);
-+
-+ if (holder == null) {
-+ return;
-+ }
-+
-+ final LevelChunk chunk = holder.getSendingChunk();
-+
-+ if (chunk != null && !chunk.isPostProcessingDone) {
-+ chunk.postProcessGeneration();
-+ }
-+ });
-+ }
-+ }
-+ }
-+
-+ public void tickMidTick() {
-+ // try to send more chunks
-+ this.trySendChunks();
-+
-+ // try to queue more chunks to load
-+ this.tryLoadChunks();
-+ }
-+
-+ static final class ChunkPriorityHolder {
-+ public final int chunkX;
-+ public final int chunkZ;
-+ public final int manhattanDistanceToPlayer;
-+ public final double priority;
-+
-+ public ChunkPriorityHolder(final int chunkX, final int chunkZ, final int manhattanDistanceToPlayer, final double priority) {
-+ this.chunkX = chunkX;
-+ this.chunkZ = chunkZ;
-+ this.manhattanDistanceToPlayer = manhattanDistanceToPlayer;
-+ this.priority = priority;
-+ }
-+ }
-+
-+ public static final class PlayerLoaderData {
-+
-+ protected static final float FOV = 110.0f;
-+ protected static final double PRIORITISED_DISTANCE = 12.0 * 16.0;
-+
-+ // Player max sprint speed is approximately 8m/s
-+ protected static final double LOOK_PRIORITY_SPEED_THRESHOLD = (10.0/20.0) * (10.0/20.0);
-+ protected static final double LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD = 3.0f;
-+
-+ protected double lastLocX = Double.NEGATIVE_INFINITY;
-+ protected double lastLocZ = Double.NEGATIVE_INFINITY;
-+
-+ protected int lastChunkX = Integer.MIN_VALUE;
-+ protected int lastChunkZ = Integer.MIN_VALUE;
-+
-+ // this is corrected so that 0 is along the positive x-axis
-+ protected float lastYaw = Float.NEGATIVE_INFINITY;
-+
-+ protected int lastSendDistance = Integer.MIN_VALUE;
-+ protected int lastLoadDistance = Integer.MIN_VALUE;
-+ protected int lastTickDistance = Integer.MIN_VALUE;
-+ protected boolean usingLookingPriority;
-+
-+ protected final ServerPlayer player;
-+ protected final PlayerChunkLoader loader;
-+
-+ // warning: modifications of this field must be aware that the loadQueue inside PlayerChunkLoader uses this field
-+ // in a comparator!
-+ protected final ArrayDeque<ChunkPriorityHolder> loadQueue = new ArrayDeque<>();
-+ protected final LongOpenHashSet sentChunks = new LongOpenHashSet();
-+ protected final LongOpenHashSet chunksToBeSent = new LongOpenHashSet();
-+
-+ protected final TreeSet<ChunkPriorityHolder> sendQueue = new TreeSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> {
-+ final int distanceCompare = Integer.compare(p1.manhattanDistanceToPlayer, p2.manhattanDistanceToPlayer);
-+ if (distanceCompare != 0) {
-+ return distanceCompare;
-+ }
-+
-+ final int coordinateXCompare = Integer.compare(p1.chunkX, p2.chunkX);
-+ if (coordinateXCompare != 0) {
-+ return coordinateXCompare;
-+ }
-+
-+ return Integer.compare(p1.chunkZ, p2.chunkZ);
-+ });
-+
-+ protected int sendViewDistance = -1;
-+ protected int loadViewDistance = -1;
-+ protected int tickViewDistance = -1;
-+
-+ protected long nextChunkSendTarget;
-+
-+ // this interval prevents bursting a lot of chunk loads
-+ protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms
-+ // this ensures the rate is kept between ticks correctly
-+ protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms
-+
-+ public long lastChunkLoad;
-+
-+ public PlayerLoaderData(final ServerPlayer player, final PlayerChunkLoader loader) {
-+ this.player = player;
-+ this.loader = loader;
-+ }
-+
-+ // these view distance methods are for api
-+ public int getTargetSendViewDistance() {
-+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
-+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance);
-+ final int clientViewDistance = this.getClientViewDistance();
-+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!GlobalConfiguration.get().chunkLoading.autoconfigSendDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance);
-+ return sendViewDistance;
-+ }
-+
-+ public void setTargetSendViewDistance(final int distance) {
-+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) {
-+ throw new IllegalArgumentException("Send view distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance);
-+ }
-+ this.sendViewDistance = distance;
-+ }
-+
-+ public int getTargetNoTickViewDistance() {
-+ return (this.loadViewDistance == -1 ? this.getLoadDistance() : this.loadViewDistance) - 1;
-+ }
-+
-+ public void setTargetNoTickViewDistance(final int distance) {
-+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) {
-+ throw new IllegalArgumentException("Simulation distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + " or -1, got: " + distance);
-+ }
-+ this.loadViewDistance = distance == -1 ? -1 : distance + 1;
-+ }
-+
-+ public int getTargetTickViewDistance() {
-+ return this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
-+ }
-+
-+ public void setTargetTickViewDistance(final int distance) {
-+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) {
-+ throw new IllegalArgumentException("View distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + " or -1, got: " + distance);
-+ }
-+ this.tickViewDistance = distance;
-+ }
-+
-+ protected int getLoadDistance() {
-+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
-+
-+ return Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance);
-+ }
-+
-+ public boolean hasSentChunk(final int chunkX, final int chunkZ) {
-+ return this.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ));
-+ }
-+
-+ public void sendChunk(final int chunkX, final int chunkZ, final Runnable onChunkSend) {
-+ if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
-+ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player,
-+ new ChunkPos(chunkX, chunkZ), new MutableObject<>(), false, true); // unloaded, loaded
-+ this.player.connection.connection.execute(onChunkSend);
-+ } else {
-+ throw new IllegalStateException();
-+ }
-+ }
-+
-+ public void unloadChunk(final int chunkX, final int chunkZ) {
-+ if (this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
-+ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player,
-+ new ChunkPos(chunkX, chunkZ), null, true, false); // unloaded, loaded
-+ }
-+ }
-+
-+ protected static boolean wantChunkLoaded(final int centerX, final int centerZ, final int chunkX, final int chunkZ,
-+ final int sendRadius) {
-+ // expect sendRadius to be = 1 + target viewable radius
-+ return ChunkMap.isChunkInRange(chunkX, chunkZ, centerX, centerZ, sendRadius);
-+ }
-+
-+ protected static boolean triangleIntersects(final double p1x, final double p1z, // triangle point
-+ final double p2x, final double p2z, // triangle point
-+ final double p3x, final double p3z, // triangle point
-+
-+ final double targetX, final double targetZ) { // point
-+ // from barycentric coordinates:
-+ // targetX = a*p1x + b*p2x + c*p3x
-+ // targetZ = a*p1z + b*p2z + c*p3z
-+ // 1.0 = a*1.0 + b*1.0 + c*1.0
-+ // where a, b, c >= 0.0
-+ // so, if any of a, b, c are less-than zero then there is no intersection.
-+
-+ // d = ((p2z - p3z)(p1x - p3x) + (p3x - p2x)(p1z - p3z))
-+ // a = ((p2z - p3z)(targetX - p3x) + (p3x - p2x)(targetZ - p3z)) / d
-+ // b = ((p3z - p1z)(targetX - p3x) + (p1x - p3x)(targetZ - p3z)) / d
-+ // c = 1.0 - a - b
-+
-+ final double d = (p2z - p3z)*(p1x - p3x) + (p3x - p2x)*(p1z - p3z);
-+ final double a = ((p2z - p3z)*(targetX - p3x) + (p3x - p2x)*(targetZ - p3z)) / d;
-+
-+ if (a < 0.0 || a > 1.0) {
-+ return false;
-+ }
-+
-+ final double b = ((p3z - p1z)*(targetX - p3x) + (p1x - p3x)*(targetZ - p3z)) / d;
-+ if (b < 0.0 || b > 1.0) {
-+ return false;
-+ }
-+
-+ final double c = 1.0 - a - b;
-+
-+ return c >= 0.0 && c <= 1.0;
-+ }
-+
-+ public void remove() {
-+ this.loader.broadcastMap.remove(this.player);
-+ this.loader.loadMap.remove(this.player);
-+ this.loader.loadTicketCleanup.remove(this.player);
-+ this.loader.tickMap.remove(this.player);
-+ }
-+
-+ protected int getClientViewDistance() {
-+ return this.player.clientViewDistance == null ? -1 : Math.max(0, this.player.clientViewDistance.intValue());
-+ }
-+
-+ public void update() {
-+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance;
-+ // load view cannot be less-than tick view + 1
-+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance);
-+ // send view cannot be greater-than load view
-+ final int clientViewDistance = this.getClientViewDistance();
-+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!GlobalConfiguration.get().chunkLoading.autoconfigSendDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance);
-+
-+ final double posX = this.player.getX();
-+ final double posZ = this.player.getZ();
-+ final float yaw = MCUtil.normalizeYaw(this.player.yRot + 90.0f); // mc yaw 0 is along the positive z axis, but obviously this is really dumb - offset so we are at positive x-axis
-+
-+ // in general, we really only want to prioritise chunks in front if we know we're moving pretty fast into them.
-+ final boolean useLookPriority = GlobalConfiguration.get().chunkLoading.enableFrustumPriority && (this.player.getDeltaMovement().horizontalDistanceSqr() > LOOK_PRIORITY_SPEED_THRESHOLD ||
-+ this.player.getAbilities().flying);
-+
-+ // make sure we're in the send queue
-+ this.loader.chunkSendWaitQueue.add(this);
-+
-+ if (
-+ // has view distance stayed the same?
-+ sendViewDistance == this.lastSendDistance
-+ && loadViewDistance == this.lastLoadDistance
-+ && tickViewDistance == this.lastTickDistance
-+
-+ && (this.usingLookingPriority ? (
-+ // has our block stayed the same (this also accounts for chunk change)?
-+ Mth.floor(this.lastLocX) == Mth.floor(posX)
-+ && Mth.floor(this.lastLocZ) == Mth.floor(posZ)
-+ ) : (
-+ // has our chunk stayed the same
-+ (Mth.floor(this.lastLocX) >> 4) == (Mth.floor(posX) >> 4)
-+ && (Mth.floor(this.lastLocZ) >> 4) == (Mth.floor(posZ) >> 4)
-+ ))
-+
-+ // has our decision about look priority changed?
-+ && this.usingLookingPriority == useLookPriority
-+
-+ // if we are currently using look priority, has our yaw stayed within recalc threshold?
-+ && (!this.usingLookingPriority || Math.abs(yaw - this.lastYaw) <= LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD)
-+ ) {
-+ // nothing we care about changed, so we're not re-calculating
-+ return;
-+ }
-+
-+ final int centerChunkX = Mth.floor(posX) >> 4;
-+ final int centerChunkZ = Mth.floor(posZ) >> 4;
-+
-+ final boolean needsChunkCenterUpdate = (centerChunkX != this.lastChunkX) || (centerChunkZ != this.lastChunkZ);
-+ this.loader.broadcastMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, sendViewDistance);
-+ this.loader.loadMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance);
-+ this.loader.loadTicketCleanup.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance + 1);
-+ this.loader.tickMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, tickViewDistance);
-+
-+ if (sendViewDistance != this.lastSendDistance) {
-+ // update the view radius for client
-+ // note that this should be after the map calls because the client wont expect unload calls not in its VD
-+ // and it's possible we decreased VD here
-+ this.player.connection.send(new ClientboundSetChunkCacheRadiusPacket(sendViewDistance));
-+ }
-+ if (tickViewDistance != this.lastTickDistance) {
-+ this.player.connection.send(new ClientboundSetSimulationDistancePacket(tickViewDistance));
-+ }
-+
-+ this.lastLocX = posX;
-+ this.lastLocZ = posZ;
-+ this.lastYaw = yaw;
-+ this.lastSendDistance = sendViewDistance;
-+ this.lastLoadDistance = loadViewDistance;
-+ this.lastTickDistance = tickViewDistance;
-+ this.usingLookingPriority = useLookPriority;
-+
-+ this.lastChunkX = centerChunkX;
-+ this.lastChunkZ = centerChunkZ;
-+
-+ // points for player "view" triangle:
-+
-+ // obviously, the player pos is a vertex
-+ final double p1x = posX;
-+ final double p1z = posZ;
-+
-+ // to the left of the looking direction
-+ final double p2x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector
-+ + p1x; // offset vector
-+ final double p2z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector
-+ + p1z; // offset vector
-+
-+ // to the right of the looking direction
-+ final double p3x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector
-+ + p1x; // offset vector
-+ final double p3z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector
-+ + p1z; // offset vector
-+
-+ // now that we have all of our points, we can recalculate the load queue
-+
-+ final List<ChunkPriorityHolder> loadQueue = new ArrayList<>();
-+
-+ // clear send queue, we are re-sorting
-+ this.sendQueue.clear();
-+ // clear chunk want set, vd/position might have changed
-+ this.chunksToBeSent.clear();
-+
-+ final int searchViewDistance = Math.max(loadViewDistance, sendViewDistance);
-+
-+ for (int dx = -searchViewDistance; dx <= searchViewDistance; ++dx) {
-+ for (int dz = -searchViewDistance; dz <= searchViewDistance; ++dz) {
-+ final int chunkX = dx + centerChunkX;
-+ final int chunkZ = dz + centerChunkZ;
-+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz));
-+ final boolean sendChunk = squareDistance <= sendViewDistance && wantChunkLoaded(centerChunkX, centerChunkZ, chunkX, chunkZ, sendViewDistance);
-+
-+ if (this.hasSentChunk(chunkX, chunkZ)) {
-+ // already sent (which means it is also loaded)
-+ if (!sendChunk) {
-+ // have sent the chunk, but don't want it anymore
-+ // unload it now
-+ this.unloadChunk(chunkX, chunkZ);
-+ }
-+ continue;
-+ }
-+
-+ final boolean loadChunk = squareDistance <= loadViewDistance;
-+
-+ final boolean prioritised = useLookPriority && triangleIntersects(
-+ // prioritisation triangle
-+ p1x, p1z, p2x, p2z, p3x, p3z,
-+
-+ // center of chunk
-+ (double)((chunkX << 4) | 8), (double)((chunkZ << 4) | 8)
-+ );
-+
-+ final int manhattanDistance = Math.abs(dx) + Math.abs(dz);
-+
-+ final double priority;
-+
-+ if (squareDistance <= GlobalConfiguration.get().chunkLoading.minLoadRadius) {
-+ // priority should be negative, and we also want to order it from center outwards
-+ // so we want (0,0) to be the smallest, and (minLoadedRadius,minLoadedRadius) to be the greatest
-+ priority = -((2 * GlobalConfiguration.get().chunkLoading.minLoadRadius + 1) - manhattanDistance);
-+ } else {
-+ if (prioritised) {
-+ // we don't prioritise these chunks above others because we also want to make sure some chunks
-+ // will be loaded if the player changes direction
-+ priority = (double)manhattanDistance / 6.0;
-+ } else {
-+ priority = (double)manhattanDistance;
-+ }
-+ }
-+
-+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, priority);
-+
-+ if (!this.loader.isChunkPlayerLoaded(chunkX, chunkZ)) {
-+ if (loadChunk) {
-+ loadQueue.add(holder);
-+ if (sendChunk) {
-+ this.chunksToBeSent.add(CoordinateUtils.getChunkKey(chunkX, chunkZ));
-+ }
-+ }
-+ } else {
-+ // loaded but not sent: so queue it!
-+ if (sendChunk) {
-+ this.sendQueue.add(holder);
-+ }
-+ }
-+ }
-+ }
-+
-+ loadQueue.sort((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> {
-+ return Double.compare(p1.priority, p2.priority);
-+ });
-+
-+ // we're modifying loadQueue, must remove
-+ this.loader.chunkLoadQueue.remove(this);
-+
-+ this.loadQueue.clear();
-+ this.loadQueue.addAll(loadQueue);
-+
-+ // must re-add
-+ this.loader.chunkLoadQueue.add(this);
-+
-+ // update the chunk center
-+ // this must be done last so that the client does not ignore any of our unload chunk packets
-+ if (needsChunkCenterUpdate) {
-+ this.player.connection.send(new ClientboundSetChunkCacheCenterPacket(centerChunkX, centerChunkZ));
-+ }
-+ }
-+ }
-+}
-diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
-index 66afd752fd7d327e141d49b477f07e1ff3645d02..2a26d03fba2f3b37f176be9e47954ef9a6cd7b3e 100644
---- a/src/main/java/net/minecraft/network/Connection.java
-+++ b/src/main/java/net/minecraft/network/Connection.java
-@@ -100,6 +100,28 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- public boolean queueImmunity = false;
- public ConnectionProtocol protocol;
- // Paper end
-+ // Paper start - add pending task queue
-+ private final Queue<Runnable> pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>();
-+ public void execute(final Runnable run) {
-+ if (this.channel == null || !this.channel.isRegistered()) {
-+ run.run();
-+ return;
-+ }
-+ final boolean queue = !this.queue.isEmpty();
-+ if (!queue) {
-+ this.channel.eventLoop().execute(run);
-+ } else {
-+ this.pendingTasks.add(run);
-+ if (this.queue.isEmpty()) {
-+ // something flushed async, dump tasks now
-+ Runnable r;
-+ while ((r = this.pendingTasks.poll()) != null) {
-+ this.channel.eventLoop().execute(r);
-+ }
-+ }
-+ }
-+ }
-+ // Paper end - add pending task queue
-
- // Paper start - allow controlled flushing
- volatile boolean canFlush = true;
-@@ -488,6 +510,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- return false;
- }
- private boolean processQueue() {
-+ try { // Paper - add pending task queue
- if (this.queue.isEmpty()) return true;
- // Paper start - make only one flush call per sendPacketQueue() call
- final boolean needsFlush = this.canFlush;
-@@ -519,6 +542,12 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- }
- }
- return true;
-+ } finally { // Paper start - add pending task queue
-+ Runnable r;
-+ while ((r = this.pendingTasks.poll()) != null) {
-+ this.channel.eventLoop().execute(r);
-+ }
-+ } // Paper end - add pending task queue
- }
- // Paper end
-
-diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
-index b575d73ae0ff2e4f09a6a1f6fb061ca3da2cedf1..6939ef9b1fe782980e77c351d8a385a573d6a8e6 100644
---- a/src/main/java/net/minecraft/server/MCUtil.java
-+++ b/src/main/java/net/minecraft/server/MCUtil.java
-@@ -636,7 +636,8 @@ public final class MCUtil {
- });
-
- worldData.addProperty("name", world.getWorld().getName());
-- worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
-+ worldData.addProperty("view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()); // Paper - replace chunk loader system
-+ worldData.addProperty("tick-view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()); // Paper - replace chunk loader system
- worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
- worldData.addProperty("keep-spawn-loaded-range", world.paperConfig().spawn.keepSpawnLoadedRange * 16);
- worldData.addProperty("visible-chunk-count", allChunks.size());
-diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 73712d6b9c828427d4c066c6d8672534575f3793..a041161dee9a857d43c83fb677dba7e90a6a5d24 100644
---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -76,6 +76,17 @@ public class ChunkHolder {
- public ServerLevel getWorld() { return chunkMap.level; } // Paper
- boolean isUpdateQueued = false; // Paper
- private final ChunkMap chunkMap; // Paper
-+ // Paper start - no-tick view distance
-+ public final LevelChunk getSendingChunk() {
-+ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used
-+ // in Chunk's neighbour callback
-+ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z);
-+ if (ret != null && ret.areNeighboursLoaded(1)) {
-+ return ret;
-+ }
-+ return null;
-+ }
-+ // Paper end - no-tick view distance
-
- // Paper start
- public void onChunkAdd() {
-@@ -273,7 +284,7 @@ public class ChunkHolder {
-
- public void blockChanged(BlockPos pos) {
- if (!pos.isInsideBuildHeightAndWorldBoundsHorizontal(levelHeightAccessor)) return; // Paper - SPIGOT-6086 for all invalid locations; avoid acquiring locks
-- LevelChunk chunk = this.getTickingChunk();
-+ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
-
- if (chunk != null) {
- int i = this.levelHeightAccessor.getSectionIndex(pos.getY());
-@@ -289,14 +300,15 @@ public class ChunkHolder {
- }
-
- public void sectionLightChanged(LightLayer lightType, int y) {
-- Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure> either = (Either) this.getFutureIfPresent(ChunkStatus.FEATURES).getNow(null); // CraftBukkit - decompile error
-+ // Paper start - no-tick view distance
-
-- if (either != null) {
-- ChunkAccess ichunkaccess = (ChunkAccess) either.left().orElse(null); // CraftBukkit - decompile error
-+ if (true) {
-+ ChunkAccess ichunkaccess = this.getAvailableChunkNow();
-
- if (ichunkaccess != null) {
- ichunkaccess.setUnsaved(true);
-- LevelChunk chunk = this.getTickingChunk();
-+ LevelChunk chunk = this.getSendingChunk();
-+ // Paper end - no-tick view distance
-
- if (chunk != null) {
- int j = this.lightEngine.getMinLightSection();
-@@ -399,9 +411,28 @@ public class ChunkHolder {
- }
-
- public void broadcast(Packet<?> packet, boolean onlyOnWatchDistanceEdge) {
-- this.playerProvider.getPlayers(this.pos, onlyOnWatchDistanceEdge).forEach((entityplayer) -> {
-- entityplayer.connection.send(packet);
-- });
-+ // Paper start - per player view distance
-+ // there can be potential desync with player's last mapped section and the view distance map, so use the
-+ // view distance map here.
-+ com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerChunkManager.broadcastMap; // Paper - replace old player chunk manager
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players = viewDistanceMap.getObjectsInRange(this.pos);
-+ if (players == null) {
-+ return;
-+ }
-+
-+ Object[] backingSet = players.getBackingSet();
-+ for (int i = 0, len = backingSet.length; i < len; ++i) {
-+ Object temp = backingSet[i];
-+ if (!(temp instanceof ServerPlayer)) {
-+ continue;
-+ }
-+ ServerPlayer player = (ServerPlayer)temp;
-+ if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) {
-+ continue;
-+ }
-+ player.connection.send(packet);
-+ }
-+ // Paper end - per player view distance
- }
-
- public CompletableFuture<Either<ChunkAccess, ChunkHolder.ChunkLoadingFailure>> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) {
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index a5e74d30045a171f5ed66a115fbd429e9ab412af..47657f20652a80f50a2e46207c9c05d1a12111b4 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -218,6 +218,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick
- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap;
- // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
-+ public final io.papermc.paper.chunk.PlayerChunkLoader playerChunkManager = new io.papermc.paper.chunk.PlayerChunkLoader(this, this.pooledLinkedPlayerHashSets); // Paper - replace chunk loader
- // Paper start - use distance map to optimise tracker
- public static boolean isLegacyTrackingEntity(Entity entity) {
- return entity.isLegacyTrackingEntity;
-@@ -237,6 +238,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- // Paper end - use distance map to optimise tracker
-
- void addPlayerToDistanceMaps(ServerPlayer player) {
-+ this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader
- int chunkX = MCUtil.getChunkCoordinate(player.getX());
- int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
- // Paper start - use distance map to optimise entity tracker
-@@ -244,7 +246,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
- int trackRange = this.entityTrackerTrackRanges[i];
-
-- trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance()));
-+ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - per player view distances
- }
- // Paper end - use distance map to optimise entity tracker
- // Note: players need to be explicitly added to distance maps before they can be updated
-@@ -274,6 +276,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- this.playerMobDistanceMap.remove(player);
- }
- // Paper end - per player mob spawning
-+ this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader
- }
-
- void updateMaps(ServerPlayer player) {
-@@ -285,7 +288,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
- int trackRange = this.entityTrackerTrackRanges[i];
-
-- trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance()));
-+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - per player view distances
- }
- // Paper end - use distance map to optimise entity tracker
- this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
-@@ -295,6 +298,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance());
- }
- // Paper end - per player mob spawning
-+ this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader
- }
- // Paper end
- // Paper start
-@@ -1447,11 +1451,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- completablefuture1.thenAcceptAsync((either) -> {
- either.ifLeft((chunk) -> {
- this.tickingGenerated.getAndIncrement();
-- MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass
--
-- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
-- this.playerLoadedChunk(entityplayer, mutableobject, chunk);
-- });
-+ // Paper - no-tick view distance - moved to Chunk neighbour update
- });
- }, (runnable) -> {
- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
-@@ -1620,33 +1620,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- int k = this.viewDistance;
-
- this.viewDistance = j;
-- this.distanceManager.updatePlayerTickets(this.viewDistance + 1);
-- Iterator objectiterator = net.minecraft.server.ChunkSystem.getUpdatingChunkHolders(this.level).iterator(); // Paper
--
-- while (objectiterator.hasNext()) {
-- ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
-- ChunkPos chunkcoordintpair = playerchunk.getPos();
-- MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass
--
-- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
-- SectionPos sectionposition = entityplayer.getLastSectionPos();
-- boolean flag = ChunkMap.isChunkInRange(chunkcoordintpair.x, chunkcoordintpair.z, sectionposition.x(), sectionposition.z(), k);
-- boolean flag1 = ChunkMap.isChunkInRange(chunkcoordintpair.x, chunkcoordintpair.z, sectionposition.x(), sectionposition.z(), this.viewDistance);
--
-- this.updateChunkTracking(entityplayer, chunkcoordintpair, mutableobject, flag, flag1);
-- });
-- }
-+ this.playerChunkManager.setLoadDistance(this.viewDistance); // Paper - replace player loader system
- }
-
- }
-
-- protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass
-+ // Paper start - replace player loader system
-+ public void setTickViewDistance(int distance) {
-+ this.playerChunkManager.setTickDistance(distance);
-+ }
-+ // Paper end - replace player loader system
-+
-+ public void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass // Paper - public
- if (player.level == this.level) {
- if (newWithinViewDistance && !oldWithinViewDistance) {
- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong());
-
- if (playerchunk != null) {
-- LevelChunk chunk = playerchunk.getTickingChunk();
-+ LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - replace chunk loader system
-
- if (chunk != null) {
- this.playerLoadedChunk(player, packet, chunk);
-@@ -1677,7 +1668,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- void dumpChunks(Writer writer) throws IOException {
- CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
-- TickingTracker tickingtracker = this.distanceManager.tickingTracker();
-+ // Paper - replace loader system
- Iterator<ChunkHolder> objectbidirectionaliterator = net.minecraft.server.ChunkSystem.getVisibleChunkHolders(this.level).iterator(); // Paper
-
- while (objectbidirectionaliterator.hasNext()) {
-@@ -1693,7 +1684,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- // CraftBukkit - decompile error
- csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> {
- return chunk.getBlockEntities().size();
-- }).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> {
-+ }).orElse(0), "Use ticket level", -1000, optional1.map((chunk) -> { // Paper - replace loader system
- return chunk.getBlockTicks().count();
- }).orElse(0), optional1.map((chunk) -> {
- return chunk.getFluidTicks().count();
-@@ -1927,15 +1918,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- this.removePlayerFromDistanceMaps(player); // Paper - distance maps
- }
-
-- for (int k = i - this.viewDistance - 1; k <= i + this.viewDistance + 1; ++k) {
-- for (int l = j - this.viewDistance - 1; l <= j + this.viewDistance + 1; ++l) {
-- if (ChunkMap.isChunkInRange(k, l, i, j, this.viewDistance)) {
-- ChunkPos chunkcoordintpair = new ChunkPos(k, l);
--
-- this.updateChunkTracking(player, chunkcoordintpair, new MutableObject(), !added, added);
-- }
-- }
-- }
-+ // Paper - handled by player chunk loader
-
- }
-
-@@ -1943,7 +1926,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- SectionPos sectionposition = SectionPos.of((EntityAccess) player);
-
- player.setLastSectionPos(sectionposition);
-- player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z()));
-+ //player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z())); // Paper - handled by player chunk loader
- return sectionposition;
- }
-
-@@ -1988,65 +1971,40 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- int k1;
- int l1;
-
-- if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) {
-- k1 = Math.min(i, i1) - this.viewDistance - 1;
-- l1 = Math.min(j, j1) - this.viewDistance - 1;
-- int i2 = Math.max(i, i1) + this.viewDistance + 1;
-- int j2 = Math.max(j, j1) + this.viewDistance + 1;
--
-- for (int k2 = k1; k2 <= i2; ++k2) {
-- for (int l2 = l1; l2 <= j2; ++l2) {
-- boolean flag3 = ChunkMap.isChunkInRange(k2, l2, i1, j1, this.viewDistance);
-- boolean flag4 = ChunkMap.isChunkInRange(k2, l2, i, j, this.viewDistance);
--
-- this.updateChunkTracking(player, new ChunkPos(k2, l2), new MutableObject(), flag3, flag4);
-- }
-- }
-- } else {
-- boolean flag5;
-- boolean flag6;
--
-- for (k1 = i1 - this.viewDistance - 1; k1 <= i1 + this.viewDistance + 1; ++k1) {
-- for (l1 = j1 - this.viewDistance - 1; l1 <= j1 + this.viewDistance + 1; ++l1) {
-- if (ChunkMap.isChunkInRange(k1, l1, i1, j1, this.viewDistance)) {
-- flag5 = true;
-- flag6 = false;
-- this.updateChunkTracking(player, new ChunkPos(k1, l1), new MutableObject(), true, false);
-- }
-- }
-- }
--
-- for (k1 = i - this.viewDistance - 1; k1 <= i + this.viewDistance + 1; ++k1) {
-- for (l1 = j - this.viewDistance - 1; l1 <= j + this.viewDistance + 1; ++l1) {
-- if (ChunkMap.isChunkInRange(k1, l1, i, j, this.viewDistance)) {
-- flag5 = false;
-- flag6 = true;
-- this.updateChunkTracking(player, new ChunkPos(k1, l1), new MutableObject(), false, true);
-- }
-- }
-- }
-- }
-+ // Paper - replaced by PlayerChunkLoader
-
- this.updateMaps(player); // Paper - distance maps
-+ this.playerChunkManager.updatePlayer(player); // Paper - respond to movement immediately
-
- }
-
- @Override
- public List<ServerPlayer> getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) {
-- Set<ServerPlayer> set = this.playerMap.getPlayers(chunkPos.toLong());
-- Builder<ServerPlayer> builder = ImmutableList.builder();
-- Iterator iterator = set.iterator();
-+ // Paper start - per player view distance
-+ // there can be potential desync with player's last mapped section and the view distance map, so use the
-+ // view distance map here.
-+ List<ServerPlayer> ret = new java.util.ArrayList<>(4);
-
-- while (iterator.hasNext()) {
-- ServerPlayer entityplayer = (ServerPlayer) iterator.next();
-- SectionPos sectionposition = entityplayer.getLastSectionPos();
-+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> players = this.playerChunkManager.broadcastMap.getObjectsInRange(chunkPos);
-+ if (players == null) {
-+ return ret;
-+ }
-
-- if (onlyOnWatchDistanceEdge && ChunkMap.isChunkOnRangeBorder(chunkPos.x, chunkPos.z, sectionposition.x(), sectionposition.z(), this.viewDistance) || !onlyOnWatchDistanceEdge && ChunkMap.isChunkInRange(chunkPos.x, chunkPos.z, sectionposition.x(), sectionposition.z(), this.viewDistance)) {
-- builder.add(entityplayer);
-+ Object[] backingSet = players.getBackingSet();
-+ for (int i = 0, len = backingSet.length; i < len; ++i) {
-+ Object temp = backingSet[i];
-+ if (!(temp instanceof ServerPlayer)) {
-+ continue;
-+ }
-+ ServerPlayer player = (ServerPlayer)temp;
-+ if (!this.playerChunkManager.isChunkSent(player, chunkPos.x, chunkPos.z, onlyOnWatchDistanceEdge)) {
-+ continue;
- }
-+ ret.add(player);
- }
-
-- return builder.build();
-+ return ret;
-+ // Paper end - per player view distance
- }
-
- public void addEntity(Entity entity) {
-@@ -2415,7 +2373,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- double vec3d_dx = player.getX() - this.entity.getX();
- double vec3d_dz = player.getZ() - this.entity.getZ();
- // Paper end - remove allocation of Vec3D here
-- double d0 = (double) Math.min(this.getEffectiveRange(), (ChunkMap.this.viewDistance - 1) * 16);
-+ double d0 = (double) Math.min(this.getEffectiveRange(), io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player) * 16); // Paper - per player view distance
- double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper
- double d2 = d0 * d0;
- boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player);
-diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index f581a9f79b2357118d912a15344ff94df3b0c50e..d1b5c25b7455174e908cd6ed66789fa700190604 100644
---- a/src/main/java/net/minecraft/server/level/DistanceManager.java
-+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -51,8 +51,8 @@ public abstract class DistanceManager {
- public final Long2ObjectOpenHashMap<SortedArraySet<Ticket<?>>> tickets = new Long2ObjectOpenHashMap();
- //private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Paper - replace ticket level propagator
- public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
-- private final TickingTracker tickingTicketsTracker = new TickingTracker();
-- private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33);
-+ //private final TickingTracker tickingTicketsTracker = new TickingTracker(); // Paper - no longer used
-+ //private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Paper - no longer used
- // Paper start use a queue, but still keep unique requirement
- public final java.util.Queue<ChunkHolder> pendingChunkUpdates = new java.util.ArrayDeque<ChunkHolder>() {
- @Override
-@@ -133,7 +133,7 @@ public abstract class DistanceManager {
- java.util.function.Predicate<Ticket<?>> removeIf = (ticket) -> {
- final boolean ret = ticket.timedOut(ticketCounter);
- if (ret) {
-- this.tickingTicketsTracker.removeTicket(currChunk[0], ticket);
-+ //this.tickingTicketsTracker.removeTicket(currChunk[0], ticket); // Paper - no longer used
- }
- return ret;
- };
-@@ -153,7 +153,7 @@ public abstract class DistanceManager {
- if (ticket.timedOut(this.ticketTickCounter)) {
- iterator.remove();
- flag = true;
-- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket);
-+ //this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); // Paper - no longer used
- }
- }
-
-@@ -184,9 +184,9 @@ public abstract class DistanceManager {
- protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator
- public boolean runAllUpdates(ChunkMap chunkStorage) {
- //this.f.a(); // Paper - no longer used
-- this.tickingTicketsTracker.runAllUpdates();
-+ //this.tickingTicketsTracker.runAllUpdates(); // Paper - no longer used
- org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
-- this.playerTicketManager.runAllUpdates();
-+ // this.playerTicketManager.runAllUpdates(); // Paper - no longer used
- boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator
-
- if (flag) {
-@@ -351,7 +351,7 @@ public abstract class DistanceManager {
- long j = chunkcoordintpair.toLong();
-
- boolean added = this.addTicket(j, ticket); // CraftBukkit
-- this.tickingTicketsTracker.addTicket(j, ticket);
-+ //this.tickingTicketsTracker.addTicket(j, ticket); // Paper - no longer used
- return added; // CraftBukkit
- }
-
-@@ -366,7 +366,7 @@ public abstract class DistanceManager {
- long j = chunkcoordintpair.toLong();
-
- boolean removed = this.removeTicket(j, ticket); // CraftBukkit
-- this.tickingTicketsTracker.removeTicket(j, ticket);
-+ //this.tickingTicketsTracker.removeTicket(j, ticket); // Paper - no longer used
- return removed; // CraftBukkit
- }
-
-@@ -488,10 +488,10 @@ public abstract class DistanceManager {
-
- if (forced) {
- this.addTicket(i, ticket);
-- this.tickingTicketsTracker.addTicket(i, ticket);
-+ //this.tickingTicketsTracker.addTicket(i, ticket); // Paper - no longer used
- } else {
- this.removeTicket(i, ticket);
-- this.tickingTicketsTracker.removeTicket(i, ticket);
-+ //this.tickingTicketsTracker.removeTicket(i, ticket); // Paper - no longer used
- }
-
- }
-@@ -504,8 +504,8 @@ public abstract class DistanceManager {
- return new ObjectOpenHashSet();
- })).add(player);
- //this.f.update(i, 0, true); // Paper - no longer used
-- this.playerTicketManager.update(i, 0, true);
-- this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
-+ //this.playerTicketManager.update(i, 0, true); // Paper - no longer used
-+ //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
- }
-
- public void removePlayer(SectionPos pos, ServerPlayer player) {
-@@ -518,8 +518,8 @@ public abstract class DistanceManager {
- if (objectset == null || objectset.isEmpty()) { // Paper
- this.playersPerChunk.remove(i);
- //this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
-- this.playerTicketManager.update(i, Integer.MAX_VALUE, false);
-- this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
-+ //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
-+ //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
- }
-
- }
-@@ -529,11 +529,17 @@ public abstract class DistanceManager {
- }
-
- public boolean inEntityTickingRange(long chunkPos) {
-- return this.tickingTicketsTracker.getLevel(chunkPos) < 32;
-+ // Paper start - replace player chunk loader system
-+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos);
-+ return holder != null && holder.isEntityTickingReady();
-+ // Paper end - replace player chunk loader system
- }
-
- public boolean inBlockTickingRange(long chunkPos) {
-- return this.tickingTicketsTracker.getLevel(chunkPos) < 33;
-+ // Paper start - replace player chunk loader system
-+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos);
-+ return holder != null && holder.isTickingReady();
-+ // Paper end - replace player chunk loader system
- }
-
- protected String getTicketDebugString(long pos) {
-@@ -543,20 +549,16 @@ public abstract class DistanceManager {
- }
-
- protected void updatePlayerTickets(int viewDistance) {
-- this.playerTicketManager.updateViewDistance(viewDistance);
-+ this.chunkMap.playerChunkManager.setTargetNoTickViewDistance(viewDistance); // Paper - route to player chunk manager
- }
-
- public void updateSimulationDistance(int simulationDistance) {
-- if (simulationDistance != this.simulationDistance) {
-- this.simulationDistance = simulationDistance;
-- this.tickingTicketsTracker.replacePlayerTicketsLevel(this.getPlayerTicketLevel());
-- }
--
-+ this.chunkMap.playerChunkManager.setTargetTickViewDistance(simulationDistance); // Paper - route to player chunk manager
- }
-
- // Paper start
- public int getSimulationDistance() {
-- return this.simulationDistance;
-+ return this.chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - route to player chunk manager
- }
- // Paper end
-
-@@ -613,10 +615,7 @@ public abstract class DistanceManager {
-
- }
-
-- @VisibleForTesting
-- TickingTracker tickingTracker() {
-- return this.tickingTicketsTracker;
-- }
-+ // Paper - replace player chunk loader
-
- public void removeTicketsOnClosing() {
- ImmutableSet<TicketType<?>> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve
-@@ -633,7 +632,7 @@ public abstract class DistanceManager {
- if (!immutableset.contains(ticket.getType())) {
- iterator.remove();
- flag = true;
-- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket);
-+ // this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); // Paper - no longer used
- }
- }
-
-@@ -672,6 +671,7 @@ public abstract class DistanceManager {
- }
- // CraftBukkit end
-
-+ /* Paper - replace old loader system
- private class ChunkTicketTracker extends ChunkTracker {
-
- public ChunkTicketTracker() {
-@@ -890,4 +890,5 @@ public abstract class DistanceManager {
- return distance <= this.viewDistance - 2;
- }
- }
-+ */ // Paper - replace old loader system
- }
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 4c82f17313e18c9dfd9b28653715b8a3242b826c..efcb80efc69a1e5ffc81b579bf535fd94e8144d7 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -667,17 +667,10 @@ public class ServerChunkCache extends ChunkSource {
- // Paper end
-
- public boolean isPositionTicking(long pos) {
-- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos);
--
-- if (playerchunk == null) {
-- return false;
-- } else if (!this.level.shouldTickBlocksAt(pos)) {
-- return false;
-- } else {
-- Either<LevelChunk, ChunkHolder.ChunkLoadingFailure> either = (Either) playerchunk.getTickingChunkFuture().getNow(null); // CraftBukkit - decompile error
--
-- return either != null && either.left().isPresent();
-- }
-+ // Paper start - replace player chunk loader system
-+ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(pos);
-+ return holder != null && holder.isTickingReady();
-+ // Paper end - replace player chunk loader system
- }
-
- public void save(boolean flush) {
-@@ -734,6 +727,7 @@ public class ServerChunkCache extends ChunkSource {
- this.level.getProfiler().popPush("chunks");
- if (tickChunks) {
- this.level.timings.chunks.startTiming(); // Paper - timings
-+ this.chunkMap.playerChunkManager.tick(); // Paper - this is mostly is to account for view distance changes
- this.tickChunks();
- this.level.timings.chunks.stopTiming(); // Paper - timings
- }
-@@ -847,13 +841,13 @@ public class ServerChunkCache extends ChunkSource {
- // Paper end - optimise chunk tick iteration
- ChunkPos chunkcoordintpair = chunk1.getPos();
-
-- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
-+ if ((true || this.level.isNaturalSpawningAllowed(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning // Paper - replace player chunk loader system
- chunk1.incrementInhabitedTime(j);
- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
- NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
- }
-
-- if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
-+ if (true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) { // Paper - replace player chunk loader system
- this.level.tickChunk(chunk1, k);
- if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper
- }
-@@ -1082,6 +1076,7 @@ public class ServerChunkCache extends ChunkSource {
- public boolean pollTask() {
- try {
- boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask(); // Paper
-+ ServerChunkCache.this.chunkMap.playerChunkManager.tickMidTick(); // Paper
- if (ServerChunkCache.this.runDistanceManagerUpdates()) {
- return true;
- } else {
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 3bb6dbdd05ed981f70556c8f905d1eeeeade30b8..e71ae32d9827d8a6fb8543abdba7627897ac9f2e 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -682,7 +682,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
- gameprofilerfiller.push("checkDespawn");
- entity.checkDespawn();
- gameprofilerfiller.pop();
-- if (this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) {
-+ if (true || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { // Paper - now always true if in the ticking list
- Entity entity1 = entity.getVehicle();
-
- if (entity1 != null) {
-@@ -715,7 +715,10 @@ public class ServerLevel extends Level implements WorldGenLevel {
-
- @Override
- public boolean shouldTickBlocksAt(long chunkPos) {
-- return this.chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(chunkPos);
-+ // Paper start - replace player chunk loader system
-+ ChunkHolder holder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos);
-+ return holder != null && holder.isTickingReady();
-+ // Paper end - replace player chunk loader system
- }
-
- protected void tickTime() {
-@@ -2459,7 +2462,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
- private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) {
- // Paper start - optimize is ticking ready type functions
- ChunkHolder chunkHolder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos);
-- return chunkHolder != null && this.chunkSource.isPositionTicking(chunkPos) && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos);
-+ return chunkHolder != null && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos); // Paper - no longer need to check with chunk source
- // Paper end
- }
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index b35b36527294dd697d146d2ad817d7911145ae8c..18c3d4aecf498f78040c27336d2ea56fd911d034 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -2475,5 +2475,5 @@ public class ServerPlayer extends Player {
- }
- // CraftBukkit end
-
-- public final int getViewDistance() { return this.getLevel().getChunkSource().chunkMap.viewDistance - 1; } // Paper - placeholder
-+ public final int getViewDistance() { throw new UnsupportedOperationException("Use PlayerChunkLoader"); } // Paper - placeholder
- }
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 67f90c75aa4858bf1575bf7b0a62b8113de7c2ea..b588e14b2826bda5b03b4fc497efcb96b566541a 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -276,7 +276,7 @@ public abstract class PlayerList {
- boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO);
-
- // Spigot - view distance
-- playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, worldserver1.spigotConfig.simulationDistance, flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation()));
-+ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat(), player.getLastDeathLocation())); // Paper - replace old player chunk management
- player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit
- playerconnection.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName())));
- playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked()));
-@@ -949,8 +949,8 @@ public abstract class PlayerList {
- // CraftBukkit start
- LevelData worlddata = worldserver1.getLevelData();
- entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionTypeId(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), flag, entityplayer1.getLastDeathLocation()));
-- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot
-- entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.spigotConfig.simulationDistance)); // Spigot
-+ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance())); // Spigot // Paper - replace old player chunk management
-+ entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance())); // Spigot // Paper - replace old player chunk management
- entityplayer1.spawnIn(worldserver1);
- entityplayer1.unsetRemoved();
- entityplayer1.connection.teleport(new Location(worldserver1.getWorld(), entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot()));
-@@ -1519,7 +1519,7 @@ public abstract class PlayerList {
-
- public void setViewDistance(int viewDistance) {
- this.viewDistance = viewDistance;
-- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance));
-+ //this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - move into setViewDistance
- Iterator iterator = this.server.getAllLevels().iterator();
-
- while (iterator.hasNext()) {
-@@ -1534,7 +1534,7 @@ public abstract class PlayerList {
-
- public void setSimulationDistance(int simulationDistance) {
- this.simulationDistance = simulationDistance;
-- this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance));
-+ //this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); // Paper - handled by playerchunkloader
- Iterator iterator = this.server.getAllLevels().iterator();
-
- while (iterator.hasNext()) {
-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 3a6e5893181ed681099f2748abca738af45ec9c9..bb51a85b33e1701c2e445305d68d3453772f73df 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
-@@ -660,7 +660,7 @@ public class EnderDragon extends Mob implements Enemy {
- // this.world.b(1028, this.getChunkCoordinates(), 0);
- //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
- for (net.minecraft.server.level.ServerPlayer player : (List<net.minecraft.server.level.ServerPlayer>) ((ServerLevel)level).players()) {
-- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch
-+ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - route to player chunk loader
- double deltaX = this.getX() - player.getX();
- double deltaZ = this.getZ() - player.getZ();
- double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
-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 b3e2e834f4f151497bf842796dd8e3a8b5143f1b..4fb40aa91e0961f1974c74c88fa68359e4ad6b16 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
-@@ -278,7 +278,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
- // this.world.globalLevelEvent(1023, new BlockPosition(this), 0);
- //int viewDistance = ((ServerLevel) this.level).getCraftServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
- for (ServerPlayer player : (List<ServerPlayer>)this.level.players()) { // Paper
-- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch
-+ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - route to player chunk loader
- double deltaX = this.getX() - player.getX();
- double deltaZ = this.getZ() - player.getZ();
- double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
-diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java
-index 0b3e9e4ed162a6d9e1f3f55b9522b75c94d13254..fa1ff2e79954089552974cefedfcbff2225738ec 100644
---- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java
-+++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java
-@@ -62,9 +62,10 @@ public class EnderEyeItem extends Item {
-
- // CraftBukkit start - Use relative location for far away sounds
- // world.b(1038, blockposition1.c(1, 0, 1), 0);
-- int viewDistance = world.getCraftServer().getViewDistance() * 16;
-+ //int viewDistance = world.getCraftServer().getViewDistance() * 16; // Paper - apply view distance patch
- BlockPos soundPos = blockposition1.offset(1, 0, 1);
- for (ServerPlayer player : world.getServer().getPlayerList().players) {
-+ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - apply view distance patch
- double deltaX = soundPos.getX() - player.getX();
- double deltaZ = soundPos.getZ() - player.getZ();
- double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
-diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 931de769a3b7c993d151f3ee8e1038d95d3899a3..30140ae5a74a511c9031b8e772e724b25e56de3d 100644
---- a/src/main/java/net/minecraft/world/level/Level.java
-+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -627,6 +627,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
-
- if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
- this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i);
-+ // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance
-+ // if copied from above
-+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((ServerLevel)this).getChunkSource().chunkMap.playerChunkManager.broadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) { // Paper - replace old player chunk management
-+ ((ServerLevel)this).getChunkSource().blockChanged(blockposition);
-+ // Paper end - per player view distance
- }
-
- if ((i & 1) != 0) {
-diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index d870cefbe5b7485f423817f4f639e3e2a304640c..2292cb0e0c1a3e0ed34b941f028136bfb0bff13e 100644
---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -191,6 +191,43 @@ public class LevelChunk extends ChunkAccess {
-
- protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) {
-
-+ // Paper start - no-tick view distance
-+ ServerChunkCache chunkProviderServer = ((ServerLevel)this.level).getChunkSource();
-+ net.minecraft.server.level.ChunkMap chunkMap = chunkProviderServer.chunkMap;
-+ // this code handles the addition of ticking tickets - the distance map handles the removal
-+ if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) {
-+ if (chunkMap.playerChunkManager.tickMap.getObjectsInRange(this.coordinateKey) != null) { // Paper - replace old player chunk loading system
-+ // now we're ready for entity ticking
-+ chunkProviderServer.mainThreadProcessor.execute(() -> {
-+ // double check that this condition still holds.
-+ if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerChunkManager.tickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) { // Paper - replace old player chunk loading system
-+ chunkMap.playerChunkManager.onChunkPlayerTickReady(this.chunkPos.x, this.chunkPos.z); // Paper - replace old player chunk
-+ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.PLAYER, LevelChunk.this.chunkPos, 31, LevelChunk.this.chunkPos); // 31 -> entity ticking, TODO check on update
-+ }
-+ });
-+ }
-+ }
-+
-+ // this code handles the chunk sending
-+ if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) {
-+ // Paper start - replace old player chunk loading system
-+ if (chunkMap.playerChunkManager.isChunkNearPlayers(this.chunkPos.x, this.chunkPos.z)) {
-+ // the post processing is expensive, so we don't want to run it unless we're actually near
-+ // a player.
-+ chunkProviderServer.mainThreadProcessor.execute(() -> {
-+ if (!LevelChunk.this.areNeighboursLoaded(1)) {
-+ return;
-+ }
-+ LevelChunk.this.postProcessGeneration();
-+ if (!LevelChunk.this.areNeighboursLoaded(1)) {
-+ return;
-+ }
-+ chunkMap.playerChunkManager.onChunkSendReady(this.chunkPos.x, this.chunkPos.z);
-+ });
-+ }
-+ // Paper end - replace old player chunk loading system
-+ }
-+ // Paper end - no-tick view distance
- }
-
- public final boolean isAnyNeighborsLoaded() {
-@@ -815,6 +852,7 @@ public class LevelChunk extends ChunkAccess {
- // Paper end - neighbour cache
- org.bukkit.Server server = this.level.getCraftServer();
- this.level.getChunkSource().addLoadedChunk(this); // Paper
-+ ((ServerLevel)this.level).getChunkSource().chunkMap.playerChunkManager.onChunkLoad(this.chunkPos.x, this.chunkPos.z); // Paper - rewrite player chunk management
- if (server != null) {
- /*
- * If it's a new world, the first few chunks are generated inside
-@@ -939,7 +977,10 @@ public class LevelChunk extends ChunkAccess {
- });
- }
-
-+ public boolean isPostProcessingDone; // Paper - replace chunk loader system
-+
- public void postProcessGeneration() {
-+ try { // Paper - replace chunk loader system
- ChunkPos chunkcoordintpair = this.getPos();
-
- for (int i = 0; i < this.postProcessing.length; ++i) {
-@@ -977,6 +1018,11 @@ public class LevelChunk extends ChunkAccess {
-
- this.pendingBlockEntities.clear();
- this.upgradeData.upgrade(this);
-+ } finally { // Paper start - replace chunk loader system
-+ this.isPostProcessingDone = true;
-+ this.level.getChunkSource().chunkMap.playerChunkManager.onChunkPostProcessing(this.chunkPos.x, this.chunkPos.z);
-+ }
-+ // Paper end - replace chunk loader system
- }
-
- @Nullable
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index 73e7181655b78f5bff90d07edfe6c5408cc08235..cf6fce4f3bddcbbae59fd128cf661e4506b9d2c5 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -483,10 +483,14 @@ public class CraftWorld extends CraftRegionAccessor implements World {
- ChunkHolder playerChunk = this.world.getChunkSource().chunkMap.getVisibleChunkIfPresent(ChunkPos.asLong(x, z));
- if (playerChunk == null) return false;
-
-- playerChunk.getTickingChunkFuture().thenAccept(either -> {
-- either.left().ifPresent(chunk -> {
-+ // Paper start - rewrite player chunk loader
-+ net.minecraft.world.level.chunk.LevelChunk chunk = playerChunk.getSendingChunk();
-+ if (chunk == null) {
-+ return false;
-+ }
-+ // Paper end - rewrite player chunk loader
- List<ServerPlayer> playersInRange = playerChunk.playerProvider.getPlayers(playerChunk.getPos(), false);
-- if (playersInRange.isEmpty()) return;
-+ if (playersInRange.isEmpty()) return true; // Paper - rewrite player chunk loader
-
- // Paper start - Anti-Xray - Bypass
- Map<Object, ClientboundLevelChunkWithLightPacket> refreshPackets = new HashMap<>();
-@@ -499,8 +503,7 @@ public class CraftWorld extends CraftRegionAccessor implements World {
- }));
- // Paper end
- }
-- });
-- });
-+ // Paper - rewrite player chunk loader
-
- return true;
- }
-@@ -2234,43 +2237,56 @@ public class CraftWorld extends CraftRegionAccessor implements World {
- // Spigot start
- @Override
- public int getViewDistance() {
-- return world.spigotConfig.viewDistance;
-+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance(); // Paper - replace old player chunk management
- }
-
- @Override
- public int getSimulationDistance() {
-- return world.spigotConfig.simulationDistance;
-+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - replace old player chunk management
- }
- // Spigot end
- // Paper start - view distance api
- @Override
- public void setViewDistance(int viewDistance) {
-- throw new UnsupportedOperationException(); //TODO
-+ // Paper start - replace old player chunk management
-+ if (viewDistance < 2 || viewDistance > 32) {
-+ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
-+ }
-+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
-+ chunkMap.setViewDistance(viewDistance);
-+ // Paper end - replace old player chunk management
- }
-
-+ // Paper start - replace old player chunk management
- @Override
- public void setSimulationDistance(int simulationDistance) {
-- throw new UnsupportedOperationException(); //TODO
-+ // Paper start - replace old player chunk management
-+ if (simulationDistance < 2 || simulationDistance > 32) {
-+ throw new IllegalArgumentException("Simulation distance " + simulationDistance + " is out of range of [2, 32]");
-+ }
-+ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
-+ chunkMap.setTickViewDistance(simulationDistance);
- }
-+ // Paper end - replace old player chunk management
-
- @Override
- public int getNoTickViewDistance() {
-- throw new UnsupportedOperationException(); //TODO
-+ return this.getViewDistance(); // Paper - replace old player chunk management
- }
-
- @Override
- public void setNoTickViewDistance(int viewDistance) {
-- throw new UnsupportedOperationException(); //TODO
-+ this.setViewDistance(viewDistance); // Paper - replace old player chunk management
- }
-
- @Override
- public int getSendViewDistance() {
-- throw new UnsupportedOperationException(); //TODO
-+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(); // Paper - replace old player chunk management
- }
-
- @Override
- public void setSendViewDistance(int viewDistance) {
-- throw new UnsupportedOperationException(); //TODO
-+ getHandle().getChunkSource().chunkMap.playerChunkManager.setSendDistance(viewDistance); // Paper - replace old player chunk management
- }
- // Paper end - view distance api
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-index b318842214ede551215fd68af033feb1c8ef6604..4f5760d782bdfeee25839c50b614301eeb8ba42f 100644
---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-@@ -546,45 +546,80 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
- }
- }
-
-+ // Paper start - implement view distances
- @Override
- public int getViewDistance() {
-- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
-+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
-+ if (data == null) {
-+ return chunkMap.playerChunkManager.getTargetNoTickViewDistance();
-+ }
-+ return data.getTargetNoTickViewDistance();
- }
-
- @Override
- public void setViewDistance(int viewDistance) {
-- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
-+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
-+ if (data == null) {
-+ throw new IllegalStateException("Player is not attached to world");
-+ }
-+
-+ data.setTargetNoTickViewDistance(viewDistance);
- }
-
- @Override
- public int getSimulationDistance() {
-- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
-+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
-+ if (data == null) {
-+ return chunkMap.playerChunkManager.getTargetTickViewDistance();
-+ }
-+ return data.getTargetTickViewDistance();
- }
-
- @Override
- public void setSimulationDistance(int simulationDistance) {
-- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
-+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
-+ if (data == null) {
-+ throw new IllegalStateException("Player is not attached to world");
-+ }
-+
-+ data.setTargetTickViewDistance(simulationDistance);
- }
-
- @Override
- public int getNoTickViewDistance() {
-- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ return this.getViewDistance();
- }
-
- @Override
- public void setNoTickViewDistance(int viewDistance) {
-- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ this.setViewDistance(viewDistance);
- }
-
- @Override
- public int getSendViewDistance() {
-- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
-+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
-+ if (data == null) {
-+ return chunkMap.playerChunkManager.getTargetSendDistance();
-+ }
-+ return data.getTargetSendViewDistance();
- }
-
- @Override
- public void setSendViewDistance(int viewDistance) {
-- throw new UnsupportedOperationException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
-+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
-+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
-+ if (data == null) {
-+ throw new IllegalStateException("Player is not attached to world");
-+ }
-+
-+ data.setTargetSendViewDistance(viewDistance);
- }
-+ // Paper end - implement view distances
-
- @Override
- public <T> T getClientOption(com.destroystokyo.paper.ClientOption<T> type) {
diff --git a/patches/removed/1.19.2-legacy-chunksystem/0859-Fix-save-problems-on-shutdown.patch b/patches/removed/1.19.2-legacy-chunksystem/0859-Fix-save-problems-on-shutdown.patch
deleted file mode 100644
index b07c4721de..0000000000
--- a/patches/removed/1.19.2-legacy-chunksystem/0859-Fix-save-problems-on-shutdown.patch
+++ /dev/null
@@ -1,74 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sat, 5 Mar 2022 17:12:52 -0800
-Subject: [PATCH] Fix save problems on shutdown
-
-- Save level.dat first, in case the shutdown is killed later
-- Force run minecraftserver tasks and the chunk source tasks
- while waiting for the chunk system to empty, as there's simply
- too much trash that could prevent them from executing during
- the chunk source tick (i.e "time left in tick" logic).
-- Set forceTicks to true, so that player packets are always
- processed so that the main process queue can be drained
-
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index df08b7afcf19ce694a87c25e8589c0c72521c5db..4d920031300a9801debc2eb39a4d3cb9d8fbb330 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -957,6 +957,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- }
- }
-
-+ // Paper start - let's be a little more intelligent around crashes
-+ // make sure level.dat saves
-+ for (ServerLevel level : this.getAllLevels()) {
-+ level.saveLevelDat();
-+ }
-+ // Paper end - let's be a little more intelligent around crashes
-+
- while (this.levels.values().stream().anyMatch((worldserver1) -> {
- return worldserver1.getChunkSource().chunkMap.hasWork();
- })) {
-@@ -969,9 +976,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- worldserver.getChunkSource().tick(() -> {
- return true;
- }, false);
-+ while (worldserver.getChunkSource().pollTask()); // Paper - drain tasks
- }
-
-- this.waitUntilNextTick();
-+ this.forceTicks = true; // Paper
-+ while (this.pollTask()); // Paper - drain tasks
- }
-
- this.saveAllChunks(false, true, false);
-@@ -1266,6 +1275,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- }
-
- private boolean haveTime() {
-+ // Paper start
-+ if (this.forceTicks) {
-+ return true;
-+ }
-+ // Paper end
- // CraftBukkit start
- if (isOversleep) return canOversleep();// Paper - because of our changes, this logic is broken
- return this.forceTicks || this.runningTask() || Util.getMillis() < (this.mayHaveDelayedTasks ? this.delayedTasksMaxNextTickTime : this.nextTickTime);
-diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index e71ae32d9827d8a6fb8543abdba7627897ac9f2e..eceaa1f2ede1c068f9090d13bf9d3b3afaa08cc3 100644
---- a/src/main/java/net/minecraft/server/level/ServerLevel.java
-+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
-@@ -1284,7 +1284,13 @@ public class ServerLevel extends Level implements WorldGenLevel {
- }
-
- }
-+ // Paper start
-+ this.saveLevelDat();
-+ }
-
-+ public void saveLevelDat() {
-+ this.saveLevelData();
-+ // Paper end
- // CraftBukkit start - moved from MinecraftServer.saveChunks
- ServerLevel worldserver1 = this;
-
diff --git a/patches/removed/1.19.3-paper-plugins/api/0019-Automatically-disable-plugins-that-fail-to-load.patch b/patches/removed/1.19.3-paper-plugins/api/0019-Automatically-disable-plugins-that-fail-to-load.patch
deleted file mode 100644
index 51fb196a9b..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0019-Automatically-disable-plugins-that-fail-to-load.patch
+++ /dev/null
@@ -1,21 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Mon, 29 Feb 2016 19:45:21 -0600
-Subject: [PATCH] Automatically disable plugins that fail to load
-
-
-diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-index 88d852c1a729ffd5951da803da424b31591c9f9a..c95833aaeda48bbdad6e8f67f98e1070171db7de 100644
---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-@@ -337,6 +337,10 @@ public final class JavaPluginLoader implements PluginLoader {
- jPlugin.setEnabled(true);
- } catch (Throwable ex) {
- server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
-+ // Paper start - Disable plugins that fail to load
-+ this.server.getPluginManager().disablePlugin(jPlugin);
-+ return;
-+ // Paper end
- }
-
- // Perhaps abort here, rather than continue going, but as it stands,
diff --git a/patches/removed/1.19.3-paper-plugins/api/0068-Make-plugins-list-alphabetical.patch b/patches/removed/1.19.3-paper-plugins/api/0068-Make-plugins-list-alphabetical.patch
deleted file mode 100644
index ec1fc9814a..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0068-Make-plugins-list-alphabetical.patch
+++ /dev/null
@@ -1,56 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: BillyGalbreath <[email protected]>
-Date: Mon, 31 Jul 2017 02:08:55 -0500
-Subject: [PATCH] Make /plugins list alphabetical
-
-
-diff --git a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
-index bcb576a4271b1ec7b1cfe6f83cf161b7d89ed2e5..4de959bbd1270d7d6ea8e5e69521bcca6abe2138 100644
---- a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
-+++ b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
-@@ -3,6 +3,9 @@ package org.bukkit.command.defaults;
- import java.util.Arrays;
- import java.util.Collections;
- import java.util.List;
-+import java.util.Map;
-+import java.util.TreeMap;
-+
- import org.bukkit.Bukkit;
- import org.bukkit.ChatColor;
- import org.bukkit.command.CommandSender;
-@@ -34,15 +37,22 @@ public class PluginsCommand extends BukkitCommand {
-
- @NotNull
- private String getPluginList() {
-- StringBuilder pluginList = new StringBuilder();
-- Plugin[] plugins = Bukkit.getPluginManager().getPlugins();
-+ // Paper start
-+ TreeMap<String, Plugin> plugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-+
-+ for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
-+ plugins.put(plugin.getDescription().getName(), plugin);
-+ }
-
-- for (Plugin plugin : plugins) {
-+ StringBuilder pluginList = new StringBuilder();
-+ for (Map.Entry<String, Plugin> entry : plugins.entrySet()) {
- if (pluginList.length() > 0) {
- pluginList.append(ChatColor.WHITE);
- pluginList.append(", ");
- }
-
-+ Plugin plugin = entry.getValue();
-+
- pluginList.append(plugin.isEnabled() ? ChatColor.GREEN : ChatColor.RED);
- pluginList.append(plugin.getDescription().getName());
-
-@@ -51,6 +61,8 @@ public class PluginsCommand extends BukkitCommand {
- }
- }
-
-- return "(" + plugins.length + "): " + pluginList.toString();
-+ return "(" + plugins.size() + "): " + pluginList.toString();
-+ // Paper end
- }
-+
- }
diff --git a/patches/removed/1.19.3-paper-plugins/api/0104-Close-Plugin-Class-Loaders-on-Disable.patch b/patches/removed/1.19.3-paper-plugins/api/0104-Close-Plugin-Class-Loaders-on-Disable.patch
deleted file mode 100644
index cecc785a80..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0104-Close-Plugin-Class-Loaders-on-Disable.patch
+++ /dev/null
@@ -1,105 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Tue, 1 May 2018 21:33:35 -0400
-Subject: [PATCH] Close Plugin Class Loaders on Disable
-
-This should close more memory leaks from /reload and disabling plugins,
-by closing the class loader and the jar file.
-
-Note: This patch is no longer necessary as upstream now also closes
-PluginClassLoaders on disable. This patch is now only to keep around
-API methods it previously added, and to add back the log message when a
-PluginClassLoader fails to disable, as upstream decided to ignore the
-exception.
-
-diff --git a/src/main/java/org/bukkit/plugin/PluginLoader.java b/src/main/java/org/bukkit/plugin/PluginLoader.java
-index cb530369e667c426c842da356c31304bb5c3ecfa..a11515b81575fc42c771a218a81fea8f05d2289d 100644
---- a/src/main/java/org/bukkit/plugin/PluginLoader.java
-+++ b/src/main/java/org/bukkit/plugin/PluginLoader.java
-@@ -78,4 +78,21 @@ public interface PluginLoader {
- * @param plugin Plugin to disable
- */
- public void disablePlugin(@NotNull Plugin plugin);
-+
-+ // Paper start - close Classloader on disable
-+ /**
-+ * This method is no longer useful as upstream has
-+ * made it so plugin classloaders are always closed on disable.
-+ * Use {@link #disablePlugin(Plugin)} instead.
-+ *
-+ * @param plugin Plugin to disable
-+ * @param closeClassloader unused
-+ * @deprecated Classloader is always closed by upstream now.
-+ */
-+ @Deprecated(forRemoval = true)
-+ // provide default to allow other PluginLoader implementations to work
-+ default public void disablePlugin(@NotNull Plugin plugin, boolean closeClassloader) {
-+ disablePlugin(plugin);
-+ }
-+ // Paper end - close Classloader on disable
- }
-diff --git a/src/main/java/org/bukkit/plugin/PluginManager.java b/src/main/java/org/bukkit/plugin/PluginManager.java
-index 03213fde8315384ec56c16031cfc606ade2e8091..94fef99525a3613dcc313a0d0b03e47a91d4117b 100644
---- a/src/main/java/org/bukkit/plugin/PluginManager.java
-+++ b/src/main/java/org/bukkit/plugin/PluginManager.java
-@@ -162,6 +162,22 @@ public interface PluginManager extends io.papermc.paper.plugin.PermissionManager
- */
- public void disablePlugin(@NotNull Plugin plugin);
-
-+ // Paper start - close Classloader on disable
-+ /**
-+ * This method is no longer useful as upstream has
-+ * made it so plugin classloaders are always closed on disable.
-+ * Use {@link #disablePlugin(Plugin)} instead.
-+ *
-+ * @param plugin Plugin to disable
-+ * @param closeClassloader unused
-+ * @deprecated Classloader is always closed by upstream now.
-+ */
-+ @Deprecated(forRemoval = true)
-+ public default void disablePlugin(@NotNull Plugin plugin, boolean closeClassloader) {
-+ this.disablePlugin(plugin);
-+ }
-+ // Paper end - close Classloader on disable
-+
- /**
- * Gets a {@link Permission} from its fully qualified name
- *
-diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-index 2b8308989fce7f8a16907f8711b362e671fdbfb6..f96164a2bc4e042bdd7c6045a9b392ad4e4dbea7 100644
---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-@@ -532,6 +532,21 @@ public final class SimplePluginManager implements PluginManager {
- }
- }
-
-+ // Paper start
-+ /**
-+ * This method is no longer useful as upstream has
-+ * made it so plugin classloaders are always closed on disable.
-+ * Use {@link #disablePlugins()} instead.
-+ *
-+ * @param closeClassloaders unused
-+ * @deprecated Classloader is always closed by upstream now.
-+ */
-+ @Deprecated(forRemoval = true)
-+ public void disablePlugins(boolean closeClassloaders) {
-+ this.disablePlugins();
-+ }
-+ // Paper end
-+
- @Override
- public void disablePlugin(@NotNull final Plugin plugin) {
- if (true) {this.paperPluginManager.disablePlugin(plugin); return;} // Paper
-diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-index ac1c884e950ce13e9e7b3a7be1378808012fafc4..e0423adf45bf5855c23259257863f67cec1c3d54 100644
---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-@@ -368,6 +368,7 @@ public final class JavaPluginLoader implements PluginLoader {
- loader.close();
- } catch (IOException ex) {
- //
-+ this.server.getLogger().log(Level.WARNING, "Error closing the PluginClassLoader for '" + plugin.getDescription().getFullName() + "'", ex); // Paper - log exception
- }
- }
- }
diff --git a/patches/removed/1.19.3-paper-plugins/api/0125-Add-an-asterisk-to-legacy-API-plugins.patch b/patches/removed/1.19.3-paper-plugins/api/0125-Add-an-asterisk-to-legacy-API-plugins.patch
deleted file mode 100644
index 1dfa20b766..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0125-Add-an-asterisk-to-legacy-API-plugins.patch
+++ /dev/null
@@ -1,66 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Phoenix616 <[email protected]>
-Date: Tue, 1 Dec 2020 14:57:02 +0100
-Subject: [PATCH] Add an asterisk to legacy API plugins
-
-Not here to name and shame, only so server admins can be aware of which
-plugins have and haven't been updated.
-
-diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java
-index 5183d3b1893fdcad9a475b747cb34df5653774fd..4af059f9b32d2a0913e6d88c6a93e101018e88a2 100644
---- a/src/main/java/org/bukkit/UnsafeValues.java
-+++ b/src/main/java/org/bukkit/UnsafeValues.java
-@@ -111,5 +111,11 @@ public interface UnsafeValues {
- default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() {
- return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher();
- }
-+
-+ boolean isSupportedApiVersion(String apiVersion);
-+
-+ static boolean isLegacyPlugin(org.bukkit.plugin.Plugin plugin) {
-+ return !Bukkit.getUnsafe().isSupportedApiVersion(plugin.getDescription().getAPIVersion());
-+ }
- // Paper end
- }
-diff --git a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
-index 4de959bbd1270d7d6ea8e5e69521bcca6abe2138..1aa58c59e1e8738bbdc77752885ff3b18b29de42 100644
---- a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
-+++ b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
-@@ -52,9 +52,15 @@ public class PluginsCommand extends BukkitCommand {
- }
-
- Plugin plugin = entry.getValue();
--
-+
- pluginList.append(plugin.isEnabled() ? ChatColor.GREEN : ChatColor.RED);
-- pluginList.append(plugin.getDescription().getName());
-+ // Paper start - Add an asterisk to legacy plugins (so admins are aware)
-+ String pluginName = plugin.getDescription().getName();
-+ if (org.bukkit.UnsafeValues.isLegacyPlugin(plugin)) {
-+ pluginName += "*";
-+ }
-+ pluginList.append(pluginName);
-+ // Paper end
-
- if (plugin.getDescription().getProvides().size() > 0) {
- pluginList.append(" (").append(String.join(", ", plugin.getDescription().getProvides())).append(")");
-diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-index e0423adf45bf5855c23259257863f67cec1c3d54..c0b9ed3a1e28c51d0195c53ec3d301dcdf58046f 100644
---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-@@ -308,7 +308,14 @@ public final class JavaPluginLoader implements PluginLoader {
- Preconditions.checkArgument(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader");
-
- if (!plugin.isEnabled()) {
-- plugin.getLogger().info("Enabling " + plugin.getDescription().getFullName());
-+ // Paper start - Add an asterisk to legacy plugins (so admins are aware)
-+ String enableMsg = "Enabling " + plugin.getDescription().getFullName();
-+ if (org.bukkit.UnsafeValues.isLegacyPlugin(plugin)) {
-+ enableMsg += "*";
-+ }
-+
-+ plugin.getLogger().info(enableMsg);
-+ // Paper end
-
- JavaPlugin jPlugin = (JavaPlugin) plugin;
-
diff --git a/patches/removed/1.19.3-paper-plugins/api/0134-Remove-deadlock-risk-in-firing-async-events.patch b/patches/removed/1.19.3-paper-plugins/api/0134-Remove-deadlock-risk-in-firing-async-events.patch
deleted file mode 100644
index 3982bba952..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0134-Remove-deadlock-risk-in-firing-async-events.patch
+++ /dev/null
@@ -1,124 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sun, 9 Sep 2018 00:32:05 -0400
-Subject: [PATCH] Remove deadlock risk in firing async events
-
-The PluginManager incorrectly used synchronization on firing any event
-that was marked as synchronous.
-
-This synchronized did not even protect any concurrency risk as
-handlers were already thread safe in terms of mutations during event
-dispatch.
-
-The way it was used, has commonly led to deadlocks on the server,
-which results in a hard crash.
-
-This change removes the synchronize and adds some protection around enable/disable
-
-diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-index 9f32b57464352c08617f6adec144111b8fcad50c..5af99aa87e6d4fbff81bd9de40484f4ed5a8a3ba 100644
---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-@@ -468,7 +468,7 @@ public final class SimplePluginManager implements PluginManager {
- * @return true if the plugin is enabled, otherwise false
- */
- @Override
-- public boolean isPluginEnabled(@Nullable Plugin plugin) {
-+ public synchronized boolean isPluginEnabled(@Nullable Plugin plugin) { // Paper - synchronize
- if ((plugin != null) && (plugins.contains(plugin))) {
- return plugin.isEnabled();
- } else {
-@@ -477,7 +477,7 @@ public final class SimplePluginManager implements PluginManager {
- }
-
- @Override
-- public void enablePlugin(@NotNull final Plugin plugin) {
-+ public synchronized void enablePlugin(@NotNull final Plugin plugin) { // Paper - synchronize
- if (!plugin.isEnabled()) {
- List<Command> pluginCommands = PluginCommandYamlParser.parse(plugin);
-
-@@ -520,7 +520,7 @@ public final class SimplePluginManager implements PluginManager {
- // Paper end
-
- @Override
-- public void disablePlugin(@NotNull final Plugin plugin) {
-+ public synchronized void disablePlugin(@NotNull final Plugin plugin) { // Paper - synchronize
- if (plugin.isEnabled()) {
- try {
- plugin.getPluginLoader().disablePlugin(plugin);
-@@ -589,6 +589,7 @@ public final class SimplePluginManager implements PluginManager {
- defaultPerms.get(false).clear();
- }
- }
-+ private void fireEvent(Event event) { callEvent(event); } // Paper - support old method incase plugin uses reflection
-
- /**
- * Calls an event with the given details.
-@@ -597,23 +598,13 @@ public final class SimplePluginManager implements PluginManager {
- */
- @Override
- public void callEvent(@NotNull Event event) {
-- if (event.isAsynchronous()) {
-- if (Thread.holdsLock(this)) {
-- throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code.");
-- }
-- if (server.isPrimaryThread()) {
-- throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from primary server thread.");
-- }
-- } else {
-- if (!server.isPrimaryThread()) {
-- throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from another thread.");
-- }
-+ // Paper - replace callEvent by merging to below method
-+ if (event.isAsynchronous() && server.isPrimaryThread()) {
-+ throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously.");
-+ } else if (!event.isAsynchronous() && !server.isPrimaryThread()) {
-+ throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
- }
-
-- fireEvent(event);
-- }
--
-- private void fireEvent(@NotNull Event event) {
- HandlerList handlers = event.getHandlers();
- RegisteredListener[] listeners = handlers.getRegisteredListeners();
-
-diff --git a/src/test/java/org/bukkit/plugin/PluginManagerTest.java b/src/test/java/org/bukkit/plugin/PluginManagerTest.java
-index c46ed2acb82db814d660459b705dd49e6d44240f..24dc87898e0fc40dfaf52f17a1bd26eccfbc7abc 100644
---- a/src/test/java/org/bukkit/plugin/PluginManagerTest.java
-+++ b/src/test/java/org/bukkit/plugin/PluginManagerTest.java
-@@ -16,7 +16,7 @@ public class PluginManagerTest {
- private static final PluginManager pm = org.bukkit.Bukkit.getServer().getPluginManager(); // Paper
-
- private final MutableObject store = new MutableObject();
--
-+/* // Paper start - remove unneeded test
- @Test
- public void testAsyncSameThread() {
- final Event event = new TestEvent(true);
-@@ -27,14 +27,14 @@ public class PluginManagerTest {
- return;
- }
- throw new IllegalStateException("No exception thrown");
-- }
-+ }*/ // Paper end
-
- @Test
- public void testSyncSameThread() {
- final Event event = new TestEvent(false);
- pm.callEvent(event);
- }
--
-+/* // Paper start - remove unneeded test
- @Test
- public void testAsyncLocked() throws InterruptedException {
- final Event event = new TestEvent(true);
-@@ -128,7 +128,7 @@ public class PluginManagerTest {
- if (store.value == null) {
- throw new IllegalStateException("No exception thrown");
- }
-- }
-+ } */ // Paper
-
- @Test
- public void testRemovePermissionByNameLower() {
diff --git a/patches/removed/1.19.3-paper-plugins/api/0193-Disable-Sync-Events-firing-Async-errors-during-shutd.patch b/patches/removed/1.19.3-paper-plugins/api/0193-Disable-Sync-Events-firing-Async-errors-during-shutd.patch
deleted file mode 100644
index 0ce11aba90..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0193-Disable-Sync-Events-firing-Async-errors-during-shutd.patch
+++ /dev/null
@@ -1,25 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Sat, 11 Apr 2020 21:38:59 -0400
-Subject: [PATCH] Disable Sync Events firing Async errors during shutdown
-
-This is how it use to behave on Paper, and this is totally destroying
-the ability to try to shut the server down gracefully during the
-shutdown process as events firing on the watchdog thread are throwing
-errors.
-
-This isn't an issue on Spigot
-
-diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-index 5af99aa87e6d4fbff81bd9de40484f4ed5a8a3ba..7937e240949bbaeb680098a674d27087e3f6acf8 100644
---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-@@ -601,7 +601,7 @@ public final class SimplePluginManager implements PluginManager {
- // Paper - replace callEvent by merging to below method
- if (event.isAsynchronous() && server.isPrimaryThread()) {
- throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously.");
-- } else if (!event.isAsynchronous() && !server.isPrimaryThread()) {
-+ } else if (!event.isAsynchronous() && !server.isPrimaryThread() && !server.isStopping() ) {
- throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
- }
-
diff --git a/patches/removed/1.19.3-paper-plugins/api/0193-Make-JavaPluginLoader-thread-safe.patch b/patches/removed/1.19.3-paper-plugins/api/0193-Make-JavaPluginLoader-thread-safe.patch
deleted file mode 100644
index 80e221872b..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0193-Make-JavaPluginLoader-thread-safe.patch
+++ /dev/null
@@ -1,53 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Trigary <[email protected]>
-Date: Wed, 15 Apr 2020 01:24:55 -0400
-Subject: [PATCH] Make JavaPluginLoader thread-safe
-
-
-diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-index c0b9ed3a1e28c51d0195c53ec3d301dcdf58046f..e40224667f2abe48d709112f038904672fa8faf5 100644
---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-@@ -53,6 +53,8 @@ import org.yaml.snakeyaml.error.YAMLException;
- public final class JavaPluginLoader implements PluginLoader {
- final Server server;
- private final Pattern[] fileFilters = new Pattern[]{Pattern.compile("\\.jar$")};
-+ private final Map<String, java.util.concurrent.locks.ReentrantReadWriteLock> classLoadLock = new java.util.HashMap<String, java.util.concurrent.locks.ReentrantReadWriteLock>(); // Paper
-+ private final Map<String, Integer> classLoadLockCount = new java.util.HashMap<String, Integer>(); // Paper
- private final List<PluginClassLoader> loaders = new CopyOnWriteArrayList<PluginClassLoader>();
- private final LibraryLoader libraryLoader;
-
-@@ -203,12 +205,33 @@ public final class JavaPluginLoader implements PluginLoader {
-
- @Nullable
- Class<?> getClassByName(final String name, boolean resolve, PluginDescriptionFile description) {
-+ // Paper start - make MT safe
-+ java.util.concurrent.locks.ReentrantReadWriteLock lock;
-+ synchronized (classLoadLock) {
-+ lock = classLoadLock.computeIfAbsent(name, (x) -> new java.util.concurrent.locks.ReentrantReadWriteLock());
-+ classLoadLockCount.compute(name, (x, prev) -> prev != null ? prev + 1 : 1);
-+ }
-+ lock.writeLock().lock();try {
-+ // Paper end
- for (PluginClassLoader loader : loaders) {
- try {
- return loader.loadClass0(name, resolve, false, ((SimplePluginManager) server.getPluginManager()).isTransitiveDepend(description, loader.plugin.getDescription()));
- } catch (ClassNotFoundException cnfe) {
- }
- }
-+ // Paper start - make MT safe
-+ } finally {
-+ synchronized (classLoadLock) {
-+ lock.writeLock().unlock();
-+ if (classLoadLockCount.get(name) == 1) {
-+ classLoadLock.remove(name);
-+ classLoadLockCount.remove(name);
-+ } else {
-+ classLoadLockCount.compute(name, (x, prev) -> prev - 1);
-+ }
-+ }
-+ }
-+ // Paper end
- return null;
- }
-
diff --git a/patches/removed/1.19.3-paper-plugins/api/0204-Prioritise-own-classes-where-possible.patch b/patches/removed/1.19.3-paper-plugins/api/0204-Prioritise-own-classes-where-possible.patch
deleted file mode 100644
index a2ad52282f..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0204-Prioritise-own-classes-where-possible.patch
+++ /dev/null
@@ -1,77 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Mariell Hoversholm <[email protected]>
-Date: Mon, 27 Apr 2020 12:31:59 +0200
-Subject: [PATCH] Prioritise own classes where possible
-
-This adds the server property `Paper.DisableClassPrioritization` to disable
-prioritization of own classes for plugins' classloaders.
-
-This value is by default not present, and this will therefore break any
-plugins which abuse behaviour related to not using their own classes
-while still loading their own. This is often an issue with failing to
-relocate or shade properly, such as when shading plugin APIs like Vault.
-
-A plugin's classloader will first look in the same jar as it is loading
-in for a requested class, then load it. It does not re-use other
-plugins' classes if it has the chance to avoid doing so.
-
-If a class is not found in the same jar as it is loading for and it does
-find it elsewhere, it will still choose the class elsewhere. This is
-intended behaviour, as it will only prioritise classes it has in its own
-jar, no other plugins' classes will be prioritised in any other order
-than the one they were registered in.
-
-The patch in general terms just loads the class in the plugin's jar
-before it starts looking elsewhere for it.
-
-diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-index e40224667f2abe48d709112f038904672fa8faf5..84ae5b84a75baca4e12221e300006bb209f8671e 100644
---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-@@ -52,6 +52,7 @@ import org.yaml.snakeyaml.error.YAMLException;
- @Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future. This implementation will be moved.
- public final class JavaPluginLoader implements PluginLoader {
- final Server server;
-+ private static final boolean DISABLE_CLASS_PRIORITIZATION = Boolean.getBoolean("Paper.DisableClassPrioritization"); // Paper
- private final Pattern[] fileFilters = new Pattern[]{Pattern.compile("\\.jar$")};
- private final Map<String, java.util.concurrent.locks.ReentrantReadWriteLock> classLoadLock = new java.util.HashMap<String, java.util.concurrent.locks.ReentrantReadWriteLock>(); // Paper
- private final Map<String, Integer> classLoadLockCount = new java.util.HashMap<String, Integer>(); // Paper
-@@ -205,6 +206,11 @@ public final class JavaPluginLoader implements PluginLoader {
-
- @Nullable
- Class<?> getClassByName(final String name, boolean resolve, PluginDescriptionFile description) {
-+ // Paper start - prioritize self
-+ return getClassByName(name, resolve, description, null);
-+ }
-+ Class<?> getClassByName(final String name, boolean resolve, PluginDescriptionFile description, PluginClassLoader requester) {
-+ // Paper end
- // Paper start - make MT safe
- java.util.concurrent.locks.ReentrantReadWriteLock lock;
- synchronized (classLoadLock) {
-@@ -212,6 +218,13 @@ public final class JavaPluginLoader implements PluginLoader {
- classLoadLockCount.compute(name, (x, prev) -> prev != null ? prev + 1 : 1);
- }
- lock.writeLock().lock();try {
-+ // Paper start - prioritize self
-+ if (!DISABLE_CLASS_PRIORITIZATION && requester != null) {
-+ try {
-+ return requester.loadClass0(name, false, false, ((SimplePluginManager) server.getPluginManager()).isTransitiveDepend(description, requester.getDescription()));
-+ } catch (ClassNotFoundException cnfe) {}
-+ }
-+ // Paper end
- // Paper end
- for (PluginClassLoader loader : loaders) {
- try {
-diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
-index ac62ad546dacfefd686bb824f60f4523631f7abc..3306c2e81ac66d649e3988aa1c142fb9fb7236fc 100644
---- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
-@@ -33,7 +33,7 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm
- public JavaPlugin getPlugin() { return plugin; } // Spigot
- private final JavaPluginLoader loader;
- private final Map<String, Class<?>> classes = new ConcurrentHashMap<String, Class<?>>();
-- private final PluginDescriptionFile description;
-+ private final PluginDescriptionFile description; PluginDescriptionFile getDescription() { return description; } // Paper
- private final File dataFolder;
- private final File file;
- private final JarFile jar;
diff --git a/patches/removed/1.19.3-paper-plugins/api/0206-Provide-a-useful-PluginClassLoader-toString.patch b/patches/removed/1.19.3-paper-plugins/api/0206-Provide-a-useful-PluginClassLoader-toString.patch
deleted file mode 100644
index f780b485d6..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0206-Provide-a-useful-PluginClassLoader-toString.patch
+++ /dev/null
@@ -1,30 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Sun, 31 May 2020 15:26:17 +0100
-Subject: [PATCH] Provide a useful PluginClassLoader#toString
-
-There are several cases where the plugin classloader may be dumped to the logs,
-however, this provides no indication of the owner of the classloader, making
-these messages effectively useless, this patch rectifies this
-
-diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
-index 064c758b19bc8c9a4e94769dd205a1bdcc972a89..fc5dc3b2f73e76976748eb013b39cae931072143 100644
---- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
-@@ -235,4 +235,16 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot
- javaPlugin.logger = this.logger; // Paper - set logger
- javaPlugin.init(loader, loader.server, description, dataFolder, file, this);
- }
-+
-+ // Paper start
-+ @Override
-+ public String toString() {
-+ JavaPlugin currPlugin = plugin != null ? plugin : pluginInit;
-+ return "PluginClassLoader{" +
-+ "plugin=" + currPlugin +
-+ ", pluginEnabled=" + (currPlugin == null ? "uninitialized" : currPlugin.isEnabled()) +
-+ ", url=" + file +
-+ '}';
-+ }
-+ // Paper end
- }
diff --git a/patches/removed/1.19.3-paper-plugins/api/0299-List-all-missing-hard-depends-not-just-first.patch b/patches/removed/1.19.3-paper-plugins/api/0299-List-all-missing-hard-depends-not-just-first.patch
deleted file mode 100644
index 57e8999ff3..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0299-List-all-missing-hard-depends-not-just-first.patch
+++ /dev/null
@@ -1,91 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jake Potrebic <[email protected]>
-Date: Tue, 18 May 2021 10:38:10 -0700
-Subject: [PATCH] List all missing hard depends not just first
-
-
-diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-index 7937e240949bbaeb680098a674d27087e3f6acf8..60988665eb358d5566e9de61aec841db3f79722c 100644
---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-@@ -258,6 +258,7 @@ public final class SimplePluginManager implements PluginManager {
-
- if (dependencies.containsKey(plugin)) {
- Iterator<String> dependencyIterator = dependencies.get(plugin).iterator();
-+ final Set<String> missingHardDependencies = new HashSet<>(dependencies.get(plugin).size()); // Paper - list all missing hard depends
-
- while (dependencyIterator.hasNext()) {
- String dependency = dependencyIterator.next();
-@@ -268,6 +269,12 @@ public final class SimplePluginManager implements PluginManager {
-
- // We have a dependency not found
- } else if (!plugins.containsKey(dependency) && !pluginsProvided.containsKey(dependency)) {
-+ // Paper start
-+ missingHardDependencies.add(dependency);
-+ }
-+ }
-+ if (!missingHardDependencies.isEmpty()) {
-+ // Paper end
- missingDependency = false;
- pluginIterator.remove();
- softDependencies.remove(plugin);
-@@ -276,9 +283,7 @@ public final class SimplePluginManager implements PluginManager {
- server.getLogger().log(
- Level.SEVERE,
- "Could not load '" + entry.getValue().getPath() + "' in folder '" + entry.getValue().getParentFile().getPath() + "'", // Paper
-- new UnknownDependencyException("Unknown dependency " + dependency + ". Please download and install " + dependency + " to run this plugin."));
-- break;
-- }
-+ new UnknownDependencyException(missingHardDependencies, plugin)); // Paper
- }
-
- if (dependencies.containsKey(plugin) && dependencies.get(plugin).isEmpty()) {
-diff --git a/src/main/java/org/bukkit/plugin/UnknownDependencyException.java b/src/main/java/org/bukkit/plugin/UnknownDependencyException.java
-index a80251eff75430863b37db1c131e22593f3fcd5e..7b2e607a21f1173d98ee84581881411176380625 100644
---- a/src/main/java/org/bukkit/plugin/UnknownDependencyException.java
-+++ b/src/main/java/org/bukkit/plugin/UnknownDependencyException.java
-@@ -26,6 +26,19 @@ public class UnknownDependencyException extends RuntimeException {
- super(message);
- }
-
-+ // Paper start
-+ /**
-+ * Create a new {@link UnknownDependencyException} with a message informing
-+ * about which dependencies are missing for what plugin.
-+ *
-+ * @param missingDependencies missing dependencies
-+ * @param pluginName plugin which is missing said dependencies
-+ */
-+ public UnknownDependencyException(final @org.jetbrains.annotations.NotNull java.util.Collection<String> missingDependencies, final @org.jetbrains.annotations.NotNull String pluginName) {
-+ this("Unknown/missing dependency plugins: [" + String.join(", ", missingDependencies) + "]. Please download and install these plugins to run '" + pluginName + "'.");
-+ }
-+ // Paper end
-+
- /**
- * Constructs a new UnknownDependencyException based on the given
- * Exception
-diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-index 4b54af83ef8fd18696d2d21ed52b61f13bff7988..8ff78fad47f6086aa289e32590f4fbec24b3d500 100644
---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-@@ -132,13 +132,19 @@ public final class JavaPluginLoader implements PluginLoader {
- ));
- }
-
-+ Set<String> missingHardDependencies = new HashSet<>(description.getDepend().size()); // Paper - list all missing hard depends
- for (final String pluginName : description.getDepend()) {
- Plugin current = server.getPluginManager().getPlugin(pluginName);
-
- if (current == null) {
-- throw new UnknownDependencyException("Unknown dependency " + pluginName + ". Please download and install " + pluginName + " to run this plugin.");
-+ missingHardDependencies.add(pluginName); // Paper - list all missing hard depends
- }
- }
-+ // Paper start - list all missing hard depends
-+ if (!missingHardDependencies.isEmpty()) {
-+ throw new UnknownDependencyException(missingHardDependencies, description.getFullName());
-+ }
-+ // Paper end
-
- server.getUnsafe().checkSupported(description);
-
diff --git a/patches/removed/1.19.3-paper-plugins/api/0318-Fix-plugin-provides-load-order.patch b/patches/removed/1.19.3-paper-plugins/api/0318-Fix-plugin-provides-load-order.patch
deleted file mode 100644
index 948a872c64..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0318-Fix-plugin-provides-load-order.patch
+++ /dev/null
@@ -1,27 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Nassim Jahnke <[email protected]>
-Date: Fri, 1 Oct 2021 09:47:00 +0200
-Subject: [PATCH] Fix plugin provides load order
-
-Fixes https://hub.spigotmc.org/jira/browse/SPIGOT-6740
-
-diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-index 2b8308989fce7f8a16907f8711b362e671fdbfb6..758faf990ba96cbcd0203e9184bcad234b4cb728 100644
---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-@@ -283,6 +283,7 @@ public final class SimplePluginManager implements PluginManager {
- } else if (!plugins.containsKey(dependency) && !pluginsProvided.containsKey(dependency)) {
- missingDependency = false;
- pluginIterator.remove();
-+ pluginsProvided.values().removeIf(s -> s.equals(plugin)); // Paper - remove provided plugins
- softDependencies.remove(plugin);
- dependencies.remove(plugin);
-
-@@ -318,6 +319,7 @@ public final class SimplePluginManager implements PluginManager {
- // We're clear to load, no more soft or hard dependencies left
- File file = plugins.get(plugin);
- pluginIterator.remove();
-+ pluginsProvided.values().removeIf(s -> s.equals(plugin)); // Paper - remove provided plugins
- missingDependency = false;
-
- try {
diff --git a/patches/removed/1.19.3-paper-plugins/api/0364-Don-t-load-plugins-prefixed-with-a-dot.patch b/patches/removed/1.19.3-paper-plugins/api/0364-Don-t-load-plugins-prefixed-with-a-dot.patch
deleted file mode 100644
index 497267bb52..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0364-Don-t-load-plugins-prefixed-with-a-dot.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Noah van der Aa <[email protected]>
-Date: Sat, 22 Jan 2022 16:35:44 +0100
-Subject: [PATCH] Don't load plugins prefixed with a dot
-
-
-diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-index d1c1df75c011d8b4e10342c864aeb206e5cac23f..94646b37c77fcb18fc4030306c431684e7e9a5cc 100644
---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-@@ -138,6 +138,7 @@ public final class SimplePluginManager implements PluginManager {
- final List<File> pluginJars = new ArrayList<>(java.util.Arrays.asList(directory.listFiles()));
- pluginJars.addAll(extraPluginJars);
- for (File file : pluginJars) {
-+ if (file.getName().startsWith(".") && !extraPluginJars.contains(file)) continue; // Don't load plugin if the file name starts with a dot, except if it's a extra plugin jar.
- // Paper end
- PluginLoader loader = null;
- for (Pattern filter : filters) {
diff --git a/patches/removed/1.19.3-paper-plugins/api/0367-Update-Folder-Uses-Plugin-Name.patch b/patches/removed/1.19.3-paper-plugins/api/0367-Update-Folder-Uses-Plugin-Name.patch
deleted file mode 100644
index 823069413c..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0367-Update-Folder-Uses-Plugin-Name.patch
+++ /dev/null
@@ -1,86 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Xemorr <[email protected]>
-Date: Fri, 1 Apr 2022 19:57:40 +0100
-Subject: [PATCH] Update Folder Uses Plugin Name
-
-
-diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-index 758faf990ba96cbcd0203e9184bcad234b4cb728..fb19ab3bd929045b4d3e227ed961a24b16b8b2bd 100644
---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
-@@ -412,7 +412,7 @@ public final class SimplePluginManager implements PluginManager {
- }
- // Paper end
-
-- checkUpdate(file);
-+ file = checkUpdate(file); // Paper - update the reference in case checkUpdate renamed it
-
- Set<Pattern> filters = fileAssociations.keySet();
- Plugin result = null;
-@@ -439,16 +439,61 @@ public final class SimplePluginManager implements PluginManager {
- return result;
- }
-
-- private void checkUpdate(@NotNull File file) {
-+ // Paper start - Update Folder Uses Plugin Name to replace
-+ /**
-+ * Replaces a plugin with a plugin of the same plugin name in the update folder.
-+ * @param file
-+ * @throws InvalidPluginException
-+ */
-+ private File checkUpdate(@NotNull File file) throws InvalidPluginException {
- if (updateDirectory == null || !updateDirectory.isDirectory()) {
-- return;
-+ return file;
- }
-+ PluginLoader pluginLoader = getPluginLoader(file);
-+ try {
-+ String pluginName = pluginLoader.getPluginDescription(file).getName();
-+ for (File updateFile : updateDirectory.listFiles()) {
-+ if (!updateFile.isFile()) continue;
-+ PluginLoader updatePluginLoader = getPluginLoader(updateFile);
-+ if (updatePluginLoader == null) continue;
-+ String updatePluginName;
-+ try {
-+ updatePluginName = updatePluginLoader.getPluginDescription(updateFile).getName();
-+ // We failed to load this data for some reason, so, we'll skip over this
-+ } catch (InvalidDescriptionException ex) {
-+ continue;
-+ }
-+ if (!pluginName.equals(updatePluginName)) continue;
-+ try {
-+ java.nio.file.Files.copy(updateFile.toPath(), file.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
-+ } catch (java.io.IOException exception) {
-+ server.getLogger().log(Level.SEVERE, "Could not copy '" + updateFile.getPath() + "' to '" + file.getPath() + "' in update plugin process", exception);
-+ continue;
-+ }
-+ File newName = new File(file.getParentFile(), updateFile.getName());
-+ file.renameTo(newName);
-+ updateFile.delete();
-+ return newName;
-+ }
-+ }
-+ catch (InvalidDescriptionException e) {
-+ throw new InvalidPluginException(e);
-+ }
-+ return file;
-+ }
-
-- File updateFile = new File(updateDirectory, file.getName());
-- if (updateFile.isFile() && FileUtil.copy(updateFile, file)) {
-- updateFile.delete();
-+ @Nullable
-+ private PluginLoader getPluginLoader(File file) {
-+ Set<Pattern> filters = fileAssociations.keySet();
-+ for (Pattern filter : filters) {
-+ Matcher match = filter.matcher(file.getName());
-+ if (match.find()) {
-+ return fileAssociations.get(filter);
-+ }
- }
-+ return null;
- }
-+ // Paper end
-
- /**
- * Checks if the given plugin is loaded and returns it when applicable
diff --git a/patches/removed/1.19.3-paper-plugins/api/0413-Future-API-Plans.patch b/patches/removed/1.19.3-paper-plugins/api/0413-Future-API-Plans.patch
deleted file mode 100644
index 7afa310be8..0000000000
--- a/patches/removed/1.19.3-paper-plugins/api/0413-Future-API-Plans.patch
+++ /dev/null
@@ -1,54 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Owen1212055 <[email protected]>
-Date: Wed, 7 Dec 2022 19:12:54 -0500
-Subject: [PATCH] Future API Plans
-
-
-diff --git a/src/main/java/org/bukkit/plugin/Plugin.java b/src/main/java/org/bukkit/plugin/Plugin.java
-index 8245de7dbf63a01336b8e291b0a3dd5e71ccd349..4eb639fbb46a0848be207149ea433455550fae1c 100644
---- a/src/main/java/org/bukkit/plugin/Plugin.java
-+++ b/src/main/java/org/bukkit/plugin/Plugin.java
-@@ -105,6 +105,7 @@ public interface Plugin extends TabExecutor {
- *
- * @return PluginLoader that controls this plugin
- */
-+ @Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future
- @NotNull
- public PluginLoader getPluginLoader();
-
-diff --git a/src/main/java/org/bukkit/plugin/PluginLoader.java b/src/main/java/org/bukkit/plugin/PluginLoader.java
-index 256e440e699942e3c9da4205bb964bdc10ec92c4..a11515b81575fc42c771a218a81fea8f05d2289d 100644
---- a/src/main/java/org/bukkit/plugin/PluginLoader.java
-+++ b/src/main/java/org/bukkit/plugin/PluginLoader.java
-@@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
- * Represents a plugin loader, which handles direct access to specific types
- * of plugins
- */
-+@Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future
- public interface PluginLoader {
-
- /**
-diff --git a/src/main/java/org/bukkit/plugin/PluginManager.java b/src/main/java/org/bukkit/plugin/PluginManager.java
-index e3460b19f3d2c27d7a9c3477467739221211f1d4..94fef99525a3613dcc313a0d0b03e47a91d4117b 100644
---- a/src/main/java/org/bukkit/plugin/PluginManager.java
-+++ b/src/main/java/org/bukkit/plugin/PluginManager.java
-@@ -23,6 +23,7 @@ public interface PluginManager extends io.papermc.paper.plugin.PermissionManager
- * @throws IllegalArgumentException Thrown when the given Class is not a
- * valid PluginLoader
- */
-+ @Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future
- public void registerInterface(@NotNull Class<? extends PluginLoader> loader) throws IllegalArgumentException;
-
- /**
-diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-index d104aa3c45bd65b3c6a521ef1ed249fb4e9de053..84ae5b84a75baca4e12221e300006bb209f8671e 100644
---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
-@@ -49,6 +49,7 @@ import org.yaml.snakeyaml.error.YAMLException;
- /**
- * Represents a Java plugin loader, allowing plugins in the form of .jar
- */
-+@Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future. This implementation will be moved.
- public final class JavaPluginLoader implements PluginLoader {
- final Server server;
- private static final boolean DISABLE_CLASS_PRIORITIZATION = Boolean.getBoolean("Paper.DisableClassPrioritization"); // Paper
diff --git a/patches/removed/1.19.3-paper-plugins/server/0223-Add-CraftMagicNumbers.isSupportedApiVersion.patch b/patches/removed/1.19.3-paper-plugins/server/0223-Add-CraftMagicNumbers.isSupportedApiVersion.patch
deleted file mode 100644
index 1ee7037234..0000000000
--- a/patches/removed/1.19.3-paper-plugins/server/0223-Add-CraftMagicNumbers.isSupportedApiVersion.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: BlackHole <[email protected]>
-Date: Sun, 15 Dec 2019 19:12:39 +0100
-Subject: [PATCH] Add CraftMagicNumbers.isSupportedApiVersion()
-
-
-diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
-index cbb161e3daa0250ae2e12e3cec972708fccaeadc..4d74e7755d3812746d9e9014046c5c22f400deec 100644
---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
-+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
-@@ -453,6 +453,11 @@ public final class CraftMagicNumbers implements UnsafeValues {
- public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() {
- return new com.destroystokyo.paper.PaperVersionFetcher();
- }
-+
-+ @Override
-+ public boolean isSupportedApiVersion(String apiVersion) {
-+ return apiVersion != null && SUPPORTED_API.contains(apiVersion);
-+ }
- // Paper end
-
- /**
diff --git a/patches/removed/1.19.4/0446-Optimize-NetworkManager-Exception-Handling-PARTIAL.patch b/patches/removed/1.19.4/0446-Optimize-NetworkManager-Exception-Handling-PARTIAL.patch
deleted file mode 100644
index 1da456ebb7..0000000000
--- a/patches/removed/1.19.4/0446-Optimize-NetworkManager-Exception-Handling-PARTIAL.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
-index 4a1148a76020089caf01f888f87afdbb35788dc0..46d9ad9cd8002770c26ed61f98593af71f534e71 100644
---- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java
-+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
-@@ -30,11 +30,15 @@ public class PacketUtils {
- try (co.aikar.timings.Timing ignored = timing.startTiming()) { // Paper - timings
- packet.handle(listener);
- } catch (Exception exception) {
-- if (listener.shouldPropagateHandlingExceptions()) {
-- throw exception;
-+ net.minecraft.network.Connection networkmanager = listener.getConnection();
-+ if (networkmanager.getPlayer() != null) {
-+ LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getScoreboardName(), networkmanager.getRemoteAddress(), exception);
-+ } else {
-+ LOGGER.error("Error whilst processing packet {} for connection from {}", packet, networkmanager.getRemoteAddress(), exception);
- }
--
-- PacketUtils.LOGGER.error("Failed to handle packet {}, suppressing error", packet, exception);
-+ net.minecraft.network.chat.Component error = net.minecraft.network.chat.Component.literal("Packet processing error");
-+ networkmanager.send(new net.minecraft.network.protocol.game.ClientboundDisconnectPacket(error), net.minecraft.network.PacketSendListener.thenRun(() -> networkmanager.disconnect(error)));
-+ networkmanager.setReadOnly();
- }
- } else {
- PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet);
diff --git a/patches/removed/1.19.4/0448-Optimize-the-advancement-data-player-iteration-to-be.patch b/patches/removed/1.19.4/0448-Optimize-the-advancement-data-player-iteration-to-be.patch
deleted file mode 100644
index 0763953dd5..0000000000
--- a/patches/removed/1.19.4/0448-Optimize-the-advancement-data-player-iteration-to-be.patch
+++ /dev/null
@@ -1,54 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Wyatt Childers <[email protected]>
-Date: Sat, 4 Jul 2020 23:07:43 -0400
-Subject: [PATCH] Optimize the advancement data player iteration to be O(N)
- rather than O(N^2)
-
-
-diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java
-index 3f7f6a43ac0bf2efb0e66dc96438febbd92113e9..0c2f12e7930646a3da53a50f38be62e0cb1ed2b7 100644
---- a/src/main/java/net/minecraft/server/PlayerAdvancements.java
-+++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java
-@@ -435,6 +435,16 @@ public class PlayerAdvancements {
- }
-
- private void ensureVisibility(Advancement advancement) {
-+ // Paper start
-+ ensureVisibility(advancement, IterationEntryPoint.ROOT);
-+ }
-+ private enum IterationEntryPoint {
-+ ROOT,
-+ ITERATOR,
-+ PARENT_OF_ITERATOR
-+ }
-+ private void ensureVisibility(Advancement advancement, IterationEntryPoint entryPoint) {
-+ // Paper end
- boolean flag = this.shouldBeVisible(advancement);
- boolean flag1 = this.visible.contains(advancement);
-
-@@ -450,15 +460,23 @@ public class PlayerAdvancements {
- }
-
- if (flag != flag1 && advancement.getParent() != null) {
-- this.ensureVisibility(advancement.getParent());
-+ // Paper start - If we're not coming from an iterator consider this to be a root entry, otherwise
-+ // market that we're entering from the parent of an iterator.
-+ this.ensureVisibility(advancement.getParent(), entryPoint == IterationEntryPoint.ITERATOR ? IterationEntryPoint.PARENT_OF_ITERATOR : IterationEntryPoint.ROOT);
- }
-
-+ // If this is true, we've went through a child iteration, entered the parent, processed the parent
-+ // and are about to reprocess the children. Stop processing here to prevent O(N^2) processing.
-+ if (entryPoint == IterationEntryPoint.PARENT_OF_ITERATOR) {
-+ return;
-+ } // Paper end
-+
- Iterator iterator = advancement.getChildren().iterator();
-
- while (iterator.hasNext()) {
- Advancement advancement1 = (Advancement) iterator.next();
-
-- this.ensureVisibility(advancement1);
-+ this.ensureVisibility(advancement1, IterationEntryPoint.ITERATOR); // Paper - Mark this call as being from iteration
- }
-
- }
diff --git a/patches/removed/1.19.4/0739-Add-config-option-for-logging-player-ip-addresses-PARTIAL.patch b/patches/removed/1.19.4/0739-Add-config-option-for-logging-player-ip-addresses-PARTIAL.patch
deleted file mode 100644
index 6243259e2a..0000000000
--- a/patches/removed/1.19.4/0739-Add-config-option-for-logging-player-ip-addresses-PARTIAL.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
-index aba1069d29389c07012ba9365fa6ae1d88fdf73c..23c81b3a96056f0b755735ad75fc81af9b046475 100644
---- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java
-+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java
-@@ -51,10 +51,11 @@ public class PacketUtils {
- packet.handle(listener);
- } catch (Exception exception) {
- net.minecraft.network.Connection networkmanager = listener.getConnection();
-+ String playerIP = io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? String.valueOf(networkmanager.getRemoteAddress()) : "<ip address withheld>"; // Paper
- if (networkmanager.getPlayer() != null) {
-- LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getScoreboardName(), networkmanager.getRemoteAddress(), exception);
-+ LOGGER.error("Error whilst processing packet {} for {}[{}]", packet, networkmanager.getPlayer().getScoreboardName(), playerIP, exception); // Paper
- } else {
-- LOGGER.error("Error whilst processing packet {} for connection from {}", packet, networkmanager.getRemoteAddress(), exception);
-+ LOGGER.error("Error whilst processing packet {} for connection from {}", packet, playerIP, exception); // Paper
- }
- net.minecraft.network.chat.Component error = net.minecraft.network.chat.Component.literal("Packet processing error");
- networkmanager.send(new net.minecraft.network.protocol.game.ClientboundDisconnectPacket(error), net.minecraft.network.PacketSendListener.thenRun(() -> networkmanager.disconnect(error)));
diff --git a/patches/removed/1.19/0752-Lag-compensate-block-breaking.patch b/patches/removed/1.19/0752-Lag-compensate-block-breaking.patch
deleted file mode 100644
index e185b2f209..0000000000
--- a/patches/removed/1.19/0752-Lag-compensate-block-breaking.patch
+++ /dev/null
@@ -1,155 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Fri, 14 Feb 2020 22:16:34 -0800
-Subject: [PATCH] Lag compensate block breaking
-
-Use time instead of ticks if ticks fall behind
-
-diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-index 919ce4ee6aafe925e520424dc0529999b536ecc7..92413fd4132b1e5b63d4be0e9cf341d2a7200df4 100644
---- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
-+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-@@ -619,4 +619,10 @@ public class PaperConfig {
- }
- }
- }
-+
-+ public static boolean lagCompensateBlockBreaking;
-+
-+ private static void lagCompensateBlockBreaking() {
-+ lagCompensateBlockBreaking = getBoolean("settings.lag-compensate-block-breaking", true);
-+ }
- }
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-index b6eef41079120fffd63f06f681378b1b628b95e0..891199d02539fa46454cd0aa7c133637e5dc8235 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java
-@@ -56,14 +56,28 @@ public class ServerPlayerGameMode {
- @Nullable
- private GameType previousGameModeForPlayer;
- private boolean isDestroyingBlock;
-- private int destroyProgressStart;
-+ private int destroyProgressStart; private long lastDigTime; // Paper - lag compensate block breaking
- private BlockPos destroyPos;
- private int gameTicks;
- private boolean hasDelayedDestroy;
- private BlockPos delayedDestroyPos;
-- private int delayedTickStart;
-+ private int delayedTickStart; private long hasDestroyedTooFastStartTime; // Paper - lag compensate block breaking
- private int lastSentState;
-
-+ // Paper start - lag compensate block breaking
-+ private int getTimeDiggingLagCompensate() {
-+ int lagCompensated = (int)((System.nanoTime() - this.lastDigTime) / (50L * 1000L * 1000L));
-+ int tickDiff = this.gameTicks - this.destroyProgressStart;
-+ return (com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking && lagCompensated > (tickDiff + 1)) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to
-+ }
-+
-+ private int getTimeDiggingTooFastLagCompensate() {
-+ int lagCompensated = (int)((System.nanoTime() - this.hasDestroyedTooFastStartTime) / (50L * 1000L * 1000L));
-+ int tickDiff = this.gameTicks - this.delayedTickStart;
-+ return (com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking && lagCompensated > (tickDiff + 1)) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to
-+ }
-+ // Paper end
-+
- public ServerPlayerGameMode(ServerPlayer player) {
- this.gameModeForPlayer = GameType.DEFAULT_MODE;
- this.destroyPos = BlockPos.ZERO;
-@@ -130,7 +144,7 @@ public class ServerPlayerGameMode {
- if (iblockdata == null || iblockdata.isAir()) { // Paper
- this.hasDelayedDestroy = false;
- } else {
-- float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart);
-+ float f = this.updateBlockBreakAnimation(iblockdata, this.delayedDestroyPos, this.getTimeDiggingTooFastLagCompensate()); // Paper - lag compensate destroying blocks
-
- if (f >= 1.0F) {
- this.hasDelayedDestroy = false;
-@@ -150,7 +164,7 @@ public class ServerPlayerGameMode {
- this.lastSentState = -1;
- this.isDestroyingBlock = false;
- } else {
-- this.incrementDestroyProgress(iblockdata, this.destroyPos, this.destroyProgressStart);
-+ this.updateBlockBreakAnimation(iblockdata, this.destroyPos, this.getTimeDiggingLagCompensate()); // Paper - lag compensate destroying
- }
- }
-
-@@ -158,6 +172,12 @@ public class ServerPlayerGameMode {
-
- private float incrementDestroyProgress(BlockState state, BlockPos pos, int i) {
- int j = this.gameTicks - i;
-+ // Paper start - change i (startTime) to totalTime
-+ return this.updateBlockBreakAnimation(state, pos, j);
-+ }
-+ private float updateBlockBreakAnimation(BlockState state, BlockPos pos, int totalTime) {
-+ int j = totalTime;
-+ // Paper end
- float f = state.getDestroyProgress(this.player, this.player.level, pos) * (float) (j + 1);
- int k = (int) (f * 10.0F);
-
-@@ -234,7 +254,7 @@ public class ServerPlayerGameMode {
- return;
- }
-
-- this.destroyProgressStart = this.gameTicks;
-+ this.destroyProgressStart = this.gameTicks; this.lastDigTime = System.nanoTime(); // Paper - lag compensate block breaking
- float f = 1.0F;
-
- iblockdata1 = this.level.getBlockState(pos);
-@@ -287,12 +307,12 @@ public class ServerPlayerGameMode {
- int j = (int) (f * 10.0F);
-
- this.level.destroyBlockProgress(this.player.getId(), pos, j);
-- this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "actual start of destroying"));
-+ if (!com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking) this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "actual start of destroying"));
- this.lastSentState = j;
- }
- } else if (action == ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK) {
- if (pos.equals(this.destroyPos)) {
-- int k = this.gameTicks - this.destroyProgressStart;
-+ int k = this.getTimeDiggingLagCompensate(); // Paper - lag compensate block breaking
-
- iblockdata1 = this.level.getBlockState(pos);
- if (!iblockdata1.isAir()) {
-@@ -309,12 +329,18 @@ public class ServerPlayerGameMode {
- this.isDestroyingBlock = false;
- this.hasDelayedDestroy = true;
- this.delayedDestroyPos = pos;
-- this.delayedTickStart = this.destroyProgressStart;
-+ this.delayedTickStart = this.destroyProgressStart; this.hasDestroyedTooFastStartTime = this.lastDigTime; // Paper - lag compensate block breaking
- }
- }
- }
-
-+ // Paper start - this can cause clients on a lagging server to think they're not currently destroying a block
-+ if (com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking) {
-+ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
-+ } else {
- this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "stopped destroying"));
-+ }
-+ // Paper end - this can cause clients on a lagging server to think they're not currently destroying a block
- } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
- this.isDestroyingBlock = false;
- if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) {
-@@ -326,7 +352,7 @@ public class ServerPlayerGameMode {
- }
-
- this.level.destroyBlockProgress(this.player.getId(), pos, -1);
-- this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "aborted destroying"));
-+ if (!com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking) this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "aborted destroying")); // Paper - this can cause clients on a lagging server to think they stopped destroying a block they're currently destroying
-
- CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
- }
-@@ -338,7 +364,13 @@ public class ServerPlayerGameMode {
-
- public void destroyAndAck(BlockPos pos, ServerboundPlayerActionPacket.Action action, String reason) {
- if (this.destroyBlock(pos)) {
-+ // Paper start - this can cause clients on a lagging server to think they're not currently destroying a block
-+ if (com.destroystokyo.paper.PaperConfig.lagCompensateBlockBreaking) {
-+ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
-+ } else {
- this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, reason));
-+ }
-+ // Paper end - this can cause clients on a lagging server to think they're not currently destroying a block
- } else {
- this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // CraftBukkit - SPIGOT-5196
- }
diff --git a/patches/removed/1.19/0823-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch b/patches/removed/1.19/0823-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch
deleted file mode 100644
index 1de8a0189e..0000000000
--- a/patches/removed/1.19/0823-Add-debug-for-invalid-GameProfiles-on-skull-blocks-i.patch
+++ /dev/null
@@ -1,65 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jason Penilla <[email protected]>
-Date: Sat, 26 Feb 2022 13:27:31 -0700
-Subject: [PATCH] Add debug for invalid GameProfiles on skull blocks/items
-
-Improves the error message for placed in world skull blocks by default,
-also adds 'Paper.debugInvalidSkullProfiles' system property which can be
-set to 'true' for extra debug info (trace of updateGameprofile caller).
-
-https://github.com/PaperMC/Paper/issues/7983 No longer functions as of 1.19
-
-diff --git a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java
-index 91fb6678214d5f23b33d5d3b65a29fdfbb003c58..e4d4dec63260c7b3ec3ed841a1e509eb67e66fb2 100644
---- a/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java
-+++ b/src/main/java/net/minecraft/world/level/block/entity/SkullBlockEntity.java
-@@ -131,13 +131,28 @@ public class SkullBlockEntity extends BlockEntity {
- updateGameprofile(this.owner, (owner) -> {
- this.owner = owner;
- this.setChanged();
-+ // Paper start
-+ }, () -> {
-+ final @Nullable Level level = this.getLevel();
-+ return "SkullBlockEntity at " + this.getBlockPos() + (level == null ? "" : (" in level: " + level.dimension().location()));
-+ // Paper end
- });
- }
-
- public static void updateGameprofile(@Nullable GameProfile owner, Consumer<GameProfile> callback) {
-+ // Paper start
-+ updateGameprofile(owner, callback, null);
-+ }
-+
-+ private static final boolean DEBUG_INVALID_SKULL_PROFILES = Boolean.getBoolean("Paper.debugInvalidSkullProfiles");
-+
-+ public static void updateGameprofile(@Nullable GameProfile owner, Consumer<GameProfile> callback, final @Nullable java.util.function.Supplier<String> debugInfo) {
- if (owner != null && !StringUtil.isNullOrEmpty(owner.getName()) && (!owner.isComplete() || !owner.getProperties().containsKey("textures")) && profileCache != null && sessionService != null) {
-+ final @Nullable Throwable trace = DEBUG_INVALID_SKULL_PROFILES ? new Throwable("updateGameprofile caller debug trace") : null;
- profileCache.getAsync(owner.getName(), (profile) -> {
- Util.PROFILE_EXECUTOR.execute(() -> { // Paper - not a good idea to use BLOCKING OPERATIONS on the worldgen executor
-+ try {
-+ // Paper end
- Util.ifElse(profile, (profilex) -> {
- Property property = Iterables.getFirst(profilex.getProperties().get("textures"), (Property)null);
- if (property == null) {
-@@ -154,6 +169,20 @@ public class SkullBlockEntity extends BlockEntity {
- callback.accept(owner);
- });
- });
-+ // Paper start
-+ } catch (final Exception ex) {
-+ if (trace != null) {
-+ ex.addSuppressed(trace);
-+ }
-+ final String ownerMessage = "Original profile: '" + owner + "'";
-+ final String debugMessage = " Run with -DPaper.debugInvalidSkullProfiles=true for further debug information.";
-+ final String message = ownerMessage + (trace == null ? debugMessage : "");
-+ if (debugInfo == null) {
-+ throw new RuntimeException(message, ex);
-+ }
-+ throw new RuntimeException(debugInfo.get() + " " + message, ex);
-+ }
-+ // Paper end
- });
- });
- } else {
diff --git a/patches/removed/1.19/no-longer-needed/0397-Broadcast-join-message-to-console.patch b/patches/removed/1.19/no-longer-needed/0397-Broadcast-join-message-to-console.patch
deleted file mode 100644
index df82b82a26..0000000000
--- a/patches/removed/1.19/no-longer-needed/0397-Broadcast-join-message-to-console.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: AvrooVulcan <[email protected]>
-Date: Fri, 17 Apr 2020 00:15:23 +0100
-Subject: [PATCH] Broadcast join message to console
-
-1.19: I think the existing method does this already - MM
-
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 936742383a6834bfd687ec48db308475f598d216..923d86766ee31f590909e398dc86b69529c29f42 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -293,7 +293,9 @@ public abstract class PlayerList {
-
- if (jm != null && !jm.equals(net.kyori.adventure.text.Component.empty())) { // Paper - Adventure
- joinMessage = PaperAdventure.asVanilla(jm); // Paper - Adventure
-- this.server.getPlayerList().broadcastAll(new ClientboundChatPacket(joinMessage, ChatType.SYSTEM, Util.NIL_UUID)); // Paper - Adventure
-+ // Paper start - Removed sendAll for loop and broadcasted to console also
-+ this.server.getPlayerList().broadcastMessage(joinMessage, ChatType.SYSTEM, Util.NIL_UUID); // Paper - Adventure
-+ // Paper end
- }
- // CraftBukkit end
-
diff --git a/patches/removed/1.20.1/0559-Throw-proper-exception-on-empty-JsonList-file.patch b/patches/removed/1.20.1/0559-Throw-proper-exception-on-empty-JsonList-file.patch
deleted file mode 100644
index 981f61ef05..0000000000
--- a/patches/removed/1.20.1/0559-Throw-proper-exception-on-empty-JsonList-file.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Mariell Hoversholm <[email protected]>
-Date: Sun, 1 Nov 2020 16:43:11 +0100
-Subject: [PATCH] Throw proper exception on empty JsonList file
-
-
-diff --git a/src/main/java/net/minecraft/server/players/StoredUserList.java b/src/main/java/net/minecraft/server/players/StoredUserList.java
-index 08173a6e0b7e46c041a81d6b51d77a0aea182525..9e8112fbc40a1d89c0f73ea4452e0fa1bb459bf4 100644
---- a/src/main/java/net/minecraft/server/players/StoredUserList.java
-+++ b/src/main/java/net/minecraft/server/players/StoredUserList.java
-@@ -182,6 +182,7 @@ public abstract class StoredUserList<K, V extends StoredUserEntry<K>> {
-
- try {
- JsonArray jsonarray = (JsonArray) StoredUserList.GSON.fromJson(bufferedreader, JsonArray.class);
-+ com.google.common.base.Preconditions.checkState(jsonarray != null, "The file \"" + this.file.getName() + "\" is either empty or corrupt"); // Paper
-
- this.map.clear();
- Iterator iterator = jsonarray.iterator();
diff --git a/patches/removed/1.20.2/0682-Allow-controlled-flushing-for-network-manager.patch b/patches/removed/1.20.2/0682-Allow-controlled-flushing-for-network-manager.patch
deleted file mode 100644
index fd7bf47eb3..0000000000
--- a/patches/removed/1.20.2/0682-Allow-controlled-flushing-for-network-manager.patch
+++ /dev/null
@@ -1,150 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sat, 4 Apr 2020 15:27:44 -0700
-Subject: [PATCH] Allow controlled flushing for network manager
-
-Only make one flush call when emptying the packet queue too
-
-This patch will be used to optimise out flush calls in later
-patches.
-
-diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
-index 0968827df66057ec73185ce688a62e8b27abba0c..95f5ec348ab24b28c19b46cea7b023a1d49998b5 100644
---- a/src/main/java/net/minecraft/network/Connection.java
-+++ b/src/main/java/net/minecraft/network/Connection.java
-@@ -126,6 +126,39 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- public ConnectionProtocol protocol;
- // Paper end
-
-+ // Paper start - allow controlled flushing
-+ volatile boolean canFlush = true;
-+ private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger();
-+ private int flushPacketsStart;
-+ private final Object flushLock = new Object();
-+
-+ public void disableAutomaticFlush() {
-+ synchronized (this.flushLock) {
-+ this.flushPacketsStart = this.packetWrites.get(); // must be volatile and before canFlush = false
-+ this.canFlush = false;
-+ }
-+ }
-+
-+ public void enableAutomaticFlush() {
-+ synchronized (this.flushLock) {
-+ this.canFlush = true;
-+ if (this.packetWrites.get() != this.flushPacketsStart) { // must be after canFlush = true
-+ this.flush(); // only make the flush call if we need to
-+ }
-+ }
-+ }
-+
-+ private final void flush() {
-+ if (this.channel.eventLoop().inEventLoop()) {
-+ this.channel.flush();
-+ } else {
-+ this.channel.eventLoop().execute(() -> {
-+ this.channel.flush();
-+ });
-+ }
-+ }
-+ // Paper end - allow controlled flushing
-+
- public Connection(PacketFlow side) {
- this.receiving = side;
- }
-@@ -298,7 +331,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- io.papermc.paper.util.MCUtil.isMainThread() && packet.isReady() && this.queue.isEmpty() &&
- (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty())
- ))) {
-- this.sendPacket(packet, callbacks);
-+ this.sendPacket(packet, callbacks, null); // Paper
- return;
- }
- // write the packets to the queue, then flush - antixray hooks there already
-@@ -322,6 +355,14 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- }
-
- private void sendPacket(Packet<?> packet, @Nullable PacketSendListener callbacks) {
-+ // Paper start - add flush parameter
-+ this.sendPacket(packet, callbacks, Boolean.TRUE);
-+ }
-+ private void sendPacket(Packet<?> packet, @Nullable PacketSendListener callbacks, Boolean flushConditional) {
-+ this.packetWrites.getAndIncrement(); // must be befeore using canFlush
-+ boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional.booleanValue();
-+ final boolean flush = effectiveFlush || packet instanceof net.minecraft.network.protocol.game.ClientboundKeepAlivePacket || packet instanceof ClientboundDisconnectPacket; // no delay for certain packets
-+ // Paper end - add flush parameter
- ConnectionProtocol enumprotocol = ConnectionProtocol.getProtocolForPacket(packet);
- ConnectionProtocol enumprotocol1 = this.getCurrentProtocol();
-
-@@ -336,16 +377,21 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- }
-
- if (this.channel.eventLoop().inEventLoop()) {
-- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1);
-+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper
- } else {
- this.channel.eventLoop().execute(() -> {
-- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1);
-+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper
- });
- }
-
- }
-
- private void doSendPacket(Packet<?> packet, @Nullable PacketSendListener callbacks, ConnectionProtocol packetState, ConnectionProtocol currentState) {
-+ // Paper start - add flush parameter
-+ this.doSendPacket(packet, callbacks, packetState, currentState, true);
-+ }
-+ private void doSendPacket(Packet<?> packet, @Nullable PacketSendListener callbacks, ConnectionProtocol packetState, ConnectionProtocol currentState, boolean flush) {
-+ // Paper end - add flush parameter
- if (packetState != currentState) {
- this.setProtocol(packetState);
- }
-@@ -359,7 +405,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
-
- try {
- // Paper end
-- ChannelFuture channelfuture = this.channel.writeAndFlush(packet);
-+ ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Paper - add flush parameter
-
- if (callbacks != null) {
- channelfuture.addListener((future) -> {
-@@ -415,6 +461,10 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- private boolean processQueue() {
- try { // Paper - add pending task queue
- if (this.queue.isEmpty()) return true;
-+ // Paper start - make only one flush call per sendPacketQueue() call
-+ final boolean needsFlush = this.canFlush;
-+ boolean hasWrotePacket = false;
-+ // Paper end - make only one flush call per sendPacketQueue() call
- // If we are on main, we are safe here in that nothing else should be processing queue off main anymore
- // But if we are not on main due to login/status, the parent is synchronized on packetQueue
- java.util.Iterator<PacketHolder> iterator = this.queue.iterator();
-@@ -422,7 +472,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- PacketHolder queued = iterator.next(); // poll -> peek
-
- // Fix NPE (Spigot bug caused by handleDisconnection())
-- if (queued == null) {
-+ if (false && queued == null) { // Paper - diff on change, this logic is redundant: iterator guarantees ret of an element - on change, hook the flush logic here
- return true;
- }
-
-@@ -434,11 +484,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
-
- Packet<?> packet = queued.packet;
- if (!packet.isReady()) {
-+ // Paper start - make only one flush call per sendPacketQueue() call
-+ if (hasWrotePacket && (needsFlush || this.canFlush)) {
-+ this.flush();
-+ }
-+ // Paper end - make only one flush call per sendPacketQueue() call
- return false;
- } else {
- iterator.remove();
- if (queued.tryMarkConsumed()) { // Paper - try to mark isConsumed flag for de-duplicating packet
-- this.sendPacket(packet, queued.listener);
-+ this.sendPacket(packet, queued.listener, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); // Paper - make only one flush call per sendPacketQueue() call
-+ hasWrotePacket = true; // Paper - make only one flush call per sendPacketQueue() call
- }
- }
- }
diff --git a/patches/removed/1.20.2/0696-Consolidate-flush-calls-for-entity-tracker-packets.patch b/patches/removed/1.20.2/0696-Consolidate-flush-calls-for-entity-tracker-packets.patch
deleted file mode 100644
index 6846952891..0000000000
--- a/patches/removed/1.20.2/0696-Consolidate-flush-calls-for-entity-tracker-packets.patch
+++ /dev/null
@@ -1,52 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Sat, 4 Apr 2020 17:00:20 -0700
-Subject: [PATCH] Consolidate flush calls for entity tracker packets
-
-Most server packets seem to be sent from here, so try to avoid
-expensive flush calls from them.
-
-This change was motivated due to local testing:
-
-- My server spawn has 130 cows in it (for testing a prev. patch)
-- Try to let 200 players join spawn
-
-Without this change, I could only get 20 players on before they
-all started timing out due to the load put on the Netty I/O threads.
-
-With this change I could get all 200 on at 0ms ping.
-
-(one of the primary issues is that my CPU is kinda trash, and having
-4 extra threads at 100% is just too much for it).
-
-So in general this patch should reduce Netty I/O thread load.
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 2a31265ac49b7a6e32105530d00952ee0c0d4331..488a253e218409b5f0b4a872cee0928578fa7582 100644
---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -656,7 +656,24 @@ public class ServerChunkCache extends ChunkSource {
- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
- gameprofilerfiller.pop();
- // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded
-+ // Paper start - controlled flush for entity tracker packets
-+ List<net.minecraft.network.Connection> disabledFlushes = new java.util.ArrayList<>(this.level.players.size());
-+ for (ServerPlayer player : this.level.players) {
-+ net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection;
-+ if (connection != null) {
-+ connection.connection.disableAutomaticFlush();
-+ disabledFlushes.add(connection.connection);
-+ }
-+ }
-+ try { // Paper end - controlled flush for entity tracker packets
- this.chunkMap.tick();
-+ // Paper start - controlled flush for entity tracker packets
-+ } finally {
-+ for (net.minecraft.network.Connection networkManager : disabledFlushes) {
-+ networkManager.enableAutomaticFlush();
-+ }
-+ }
-+ // Paper end - controlled flush for entity tracker packets
- }
- }
-
diff --git a/patches/removed/1.20.2/0704-Optimise-non-flush-packet-sending.patch b/patches/removed/1.20.2/0704-Optimise-non-flush-packet-sending.patch
deleted file mode 100644
index 702421e3eb..0000000000
--- a/patches/removed/1.20.2/0704-Optimise-non-flush-packet-sending.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Tue, 22 Sep 2020 01:49:19 -0700
-Subject: [PATCH] Optimise non-flush packet sending
-
-Places like entity tracking make heavy use of packet sending,
-and internally netty will use some very expensive thread wakeup
-calls when scheduling.
-
-Thanks to various hacks in ProtocolLib as well as other
-plugins, we cannot simply use a queue of packets to group
-send on execute. We have to call execute for each packet.
-
-Tux's suggestion here is exactly what was needed - tag
-the Runnable indicating it should not make a wakeup call.
-
-Big thanks to Tux for making this possible as I had given
-up on this optimisation before he came along.
-
-Locally this patch drops the entity tracker tick by a full 1.5x.
-
-diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
-index 9a9924a645c71e0cec30e29a9defcd1e22e2e8ef..15798ed13488b8b8b16ebee557dce18e3dc51708 100644
---- a/src/main/java/net/minecraft/network/Connection.java
-+++ b/src/main/java/net/minecraft/network/Connection.java
-@@ -437,9 +437,19 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
- if (this.channel.eventLoop().inEventLoop()) {
- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper
- } else {
-+ // Paper start - optimise packets that are not flushed
-+ // note: since the type is not dynamic here, we need to actually copy the old executor code
-+ // into two branches. On conflict, just re-copy - no changes were made inside the executor code.
-+ if (!flush) {
-+ io.netty.util.concurrent.AbstractEventExecutor.LazyRunnable run = () -> {
-+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter
-+ };
-+ this.channel.eventLoop().execute(run);
-+ } else { // Paper end - optimise packets that are not flushed
- this.channel.eventLoop().execute(() -> {
-- this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper
-+ this.doSendPacket(packet, callbacks, enumprotocol, enumprotocol1, flush); // Paper - add flush parameter // Paper - diff on change
- });
-+ } // Paper
- }
-
- }
diff --git a/patches/removed/1.20.2/0724-Add-config-option-for-logging-player-ip-addresses.patch b/patches/removed/1.20.2/0724-Add-config-option-for-logging-player-ip-addresses.patch
deleted file mode 100644
index a96c1d39be..0000000000
--- a/patches/removed/1.20.2/0724-Add-config-option-for-logging-player-ip-addresses.patch
+++ /dev/null
@@ -1,61 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Noah van der Aa <[email protected]>
-Date: Tue, 5 Oct 2021 20:04:21 +0200
-Subject: [PATCH] Add config option for logging player ip addresses
-
-
-diff --git a/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java b/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java
-index 21393ad40095a4049e5b6871169b2db7aa92d13c..e6553b936dac1eb25a310d1a33acb0b1a5e646d2 100644
---- a/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java
-+++ b/src/main/java/net/minecraft/server/network/LegacyQueryHandler.java
-@@ -185,7 +185,7 @@ public class LegacyQueryHandler extends ChannelInboundHandlerAdapter {
- buf.release();
- this.buf = null;
-
-- LOGGER.debug("Ping: (1.6) from {}", ctx.channel().remoteAddress());
-+ LOGGER.debug("Ping: (1.6) from {}", io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? ctx.channel().remoteAddress() : "<ip address withheld>"); // Paper
-
- InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port);
- com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(
-diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-index e5e2f763d9b4b955df79ea0c4c79565be1fe59f0..2beddfc0532c3835d50724551e3d46cb0d7d2290 100644
---- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
-@@ -207,7 +207,7 @@ public class ServerConnectionListener {
- throw new ReportedException(CrashReport.forThrowable(exception, "Ticking memory connection"));
- }
-
-- ServerConnectionListener.LOGGER.warn("Failed to handle packet for {}", networkmanager.getRemoteAddress(), exception);
-+ ServerConnectionListener.LOGGER.warn("Failed to handle packet for {}", io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? String.valueOf(networkmanager.getRemoteAddress()) : "<ip address withheld>", exception); // Paper
- MutableComponent ichatmutablecomponent = Component.literal("Internal server error");
-
- networkmanager.send(new ClientboundDisconnectPacket(ichatmutablecomponent), PacketSendListener.thenRun(() -> {
-diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-index 595779cfd0ee1c405d7936f00a7cae1706125e7f..ed3af916dfa875dd0a5f1e730d20d11efd6419c6 100644
---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-@@ -206,7 +206,10 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
- }
-
- public String getUserName() {
-- return this.gameProfile != null ? this.gameProfile + " (" + this.connection.getRemoteAddress() + ")" : String.valueOf(this.connection.getRemoteAddress());
-+ // Paper start
-+ String ip = io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? String.valueOf(this.connection.getRemoteAddress()) : "<ip address withheld>";
-+ return this.gameProfile != null ? this.gameProfile + " (" + ip + ")" : String.valueOf(ip);
-+ // Paper end
- }
-
- @Override
-diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index c3fb1bbc0e2a44160e86b5bb4ab88f78991fa9e6..ffa831673805201932c36b814f4439f3bb5c4c04 100644
---- a/src/main/java/net/minecraft/server/players/PlayerList.java
-+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -255,7 +255,7 @@ public abstract class PlayerList {
- String s1 = "local";
-
- if (connection.getRemoteAddress() != null) {
-- s1 = connection.getRemoteAddress().toString();
-+ s1 = io.papermc.paper.configuration.GlobalConfiguration.get().logging.logPlayerIpAddresses ? connection.getRemoteAddress().toString() : "<ip address withheld>"; // Paper
- }
-
- // Spigot start - spawn location event
diff --git a/patches/removed/1.20.2/0839-Don-t-print-component-in-resource-pack-rejection-mes.patch b/patches/removed/1.20.2/0839-Don-t-print-component-in-resource-pack-rejection-mes.patch
deleted file mode 100644
index 8d06a57105..0000000000
--- a/patches/removed/1.20.2/0839-Don-t-print-component-in-resource-pack-rejection-mes.patch
+++ /dev/null
@@ -1,19 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: pop4959 <[email protected]>
-Date: Fri, 1 Jul 2022 22:00:06 -0700
-Subject: [PATCH] Don't print component in resource pack rejection message
-
-
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index 42bb941af5eac6e4a4d79a59be9e1ac56468c4b4..5c4a4b7efff2a00d7a35d902b4e18e02d4489122 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -2037,7 +2037,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Tic
- public void handleResourcePackResponse(ServerboundResourcePackPacket packet) {
- PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
- if (packet.getAction() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) {
-- ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getName());
-+ ServerGamePacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack rejection", this.player.getGameProfile().getName()); // Paper - Don't print component in resource pack rejection message
- this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - add cause
- }
- // Paper start
diff --git a/patches/removed/1.20.3/0061-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch b/patches/removed/1.20.3/0061-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch
deleted file mode 100644
index 2150c56cd7..0000000000
--- a/patches/removed/1.20.3/0061-Don-t-nest-if-we-don-t-need-to-when-cerealising-text.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: kashike <[email protected]>
-Date: Tue, 8 Mar 2016 18:28:43 -0800
-Subject: [PATCH] Don't nest if we don't need to when cerealising text
- components
-
-
-diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java
-index 55e21c7b13826f60e3c656f76e1507e0242e0af3..1387e3597c43fd652f2fc82ca6fc2e83039604e2 100644
---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java
-+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundSystemChatPacket.java
-@@ -14,7 +14,7 @@ public record ClientboundSystemChatPacket(@javax.annotation.Nullable net.kyori.a
- }
-
- public ClientboundSystemChatPacket(net.md_5.bungee.api.chat.BaseComponent[] content, boolean overlay) {
-- this(null, net.md_5.bungee.chat.ComponentSerializer.toString(content), overlay); // Paper - Adventure
-+ this(null, improveBungeeComponentSerialization(content), overlay); // Paper - Adventure
- }
- // Spigot end
- // Paper start
-@@ -25,6 +25,14 @@ public record ClientboundSystemChatPacket(@javax.annotation.Nullable net.kyori.a
- public ClientboundSystemChatPacket(net.kyori.adventure.text.Component content, boolean overlay) {
- this(content, null, overlay);
- }
-+
-+ private static String improveBungeeComponentSerialization(net.md_5.bungee.api.chat.BaseComponent[] content) {
-+ if (content.length == 1) {
-+ return net.md_5.bungee.chat.ComponentSerializer.toString(content[0]);
-+ } else {
-+ return net.md_5.bungee.chat.ComponentSerializer.toString(content);
-+ }
-+ }
- // Paper end
-
- public ClientboundSystemChatPacket(FriendlyByteBuf buf) {
diff --git a/patches/removed/1.20.3/0130-Do-not-let-armorstands-drown.patch b/patches/removed/1.20.3/0130-Do-not-let-armorstands-drown.patch
deleted file mode 100644
index 21cc9d4ed3..0000000000
--- a/patches/removed/1.20.3/0130-Do-not-let-armorstands-drown.patch
+++ /dev/null
@@ -1,23 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Zach Brown <[email protected]>
-Date: Sat, 18 Feb 2017 19:29:58 -0600
-Subject: [PATCH] Do not let armorstands drown
-
-
-diff --git a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
-index d79fbc3e1cab0d4ec38ae25325e55eb23a1b5a19..37c19685049ef6b267a74a4323dc4ec33159c3d2 100644
---- a/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
-+++ b/src/main/java/net/minecraft/world/entity/decoration/ArmorStand.java
-@@ -959,5 +959,12 @@ public class ArmorStand extends LivingEntity {
- super.move(type, movement);
- }
- }
-+
-+ // Paper start
-+ @Override
-+ public boolean canBreatheUnderwater() { // Skips a bit of damage handling code, probably a micro-optimization
-+ return true;
-+ }
-+ // Paper end
- // Paper end
- }
diff --git a/patches/removed/1.20.3/0268-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch b/patches/removed/1.20.3/0268-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch
deleted file mode 100644
index f6e0c2c41d..0000000000
--- a/patches/removed/1.20.3/0268-MC-50319-Check-other-worlds-for-shooter-of-projectil.patch
+++ /dev/null
@@ -1,35 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 17 Oct 2018 19:17:27 -0400
-Subject: [PATCH] MC-50319: Check other worlds for shooter of projectiles
-
-Say a player shoots an arrow through a nether portal, the game
-would lose the shooter for determining things such as Player Kills,
-because the entity is in another world.
-
-If the projectile fails to find the shooter in the current world, check
-other worlds.
-
-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 01a2c6c3ee4e1500b6ee9986943f84dbe8663860..fec4897ffc07f71efb8725efea341ba2878a1462 100644
---- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
-+++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java
-@@ -67,6 +67,18 @@ public abstract class Projectile extends Entity implements TraceableEntity {
- ServerLevel worldserver = (ServerLevel) world;
-
- this.cachedOwner = worldserver.getEntity(this.ownerUUID);
-+ // Paper start - check all worlds
-+ if (this.cachedOwner == null) {
-+ for (final ServerLevel level : this.level().getServer().getAllLevels()) {
-+ if (level == this.level()) continue;
-+ final Entity entity = level.getEntity(this.ownerUUID);
-+ if (entity != null) {
-+ this.cachedOwner = entity;
-+ break;
-+ }
-+ }
-+ }
-+ // Paper end
- return this.cachedOwner;
- }
- }
diff --git a/patches/removed/1.20.3/0395-Optimize-brigadier-child-sorting-performance.patch b/patches/removed/1.20.3/0395-Optimize-brigadier-child-sorting-performance.patch
deleted file mode 100644
index 3651f34b15..0000000000
--- a/patches/removed/1.20.3/0395-Optimize-brigadier-child-sorting-performance.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: virustotalop <[email protected]>
-Date: Thu, 16 Apr 2020 20:51:32 -0700
-Subject: [PATCH] Optimize brigadier child sorting performance
-
-
-diff --git a/src/main/java/com/mojang/brigadier/tree/CommandNode.java b/src/main/java/com/mojang/brigadier/tree/CommandNode.java
-index 3384501f83d445f45aa8233e98c7597daa67b8ef..20a7cdf87f307878d66922aaac0c60cff218e46c 100644
---- a/src/main/java/com/mojang/brigadier/tree/CommandNode.java
-+++ b/src/main/java/com/mojang/brigadier/tree/CommandNode.java
-@@ -26,7 +26,7 @@ import java.util.function.Predicate;
- import net.minecraft.commands.CommandSourceStack;
-
- public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
-- private final Map<String, CommandNode<S>> children = new LinkedHashMap<>();
-+ private Map<String, CommandNode<S>> children = com.google.common.collect.Maps.newTreeMap(); // Paper - Switch to tree map for automatic sorting
- private final Map<String, LiteralCommandNode<S>> literals = new LinkedHashMap<>();
- private final Map<String, ArgumentCommandNode<S, ?>> arguments = new LinkedHashMap<>();
- public Predicate<S> requirement;
-@@ -107,6 +107,8 @@ public abstract class CommandNode<S> implements Comparable<CommandNode<S>> {
- this.arguments.put(node.getName(), (ArgumentCommandNode<S, ?>) node);
- }
- }
-+
-+ // Paper - Remove manual sorting, it is no longer needed
- }
-
- public void findAmbiguities(final AmbiguityConsumer<S> consumer) {
diff --git a/patches/removed/1.20.3/0748-Kick-on-main-for-illegal-chat.patch b/patches/removed/1.20.3/0748-Kick-on-main-for-illegal-chat.patch
deleted file mode 100644
index 6c8e0a4cba..0000000000
--- a/patches/removed/1.20.3/0748-Kick-on-main-for-illegal-chat.patch
+++ /dev/null
@@ -1,45 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Nassim Jahnke <[email protected]>
-Date: Mon, 17 Jan 2022 19:47:19 +0100
-Subject: [PATCH] Kick on main for illegal chat
-
-Makes the PlayerKickEvent fire on the main thread for
-illegal characters or chat out-of-order errors.
-
-TODO: Check all the missing schedule on mains? (1.20.3)
- - not needed anymore, there is a check inside disconnect if off main
-
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index b650e7b53bea147be0b5d88449d41f03a971ca97..09465bee578a440c0da6961dde66083cd097f5e5 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -2049,7 +2049,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
- }
- // CraftBukkit end
- if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.message())) {
-+ this.server.scheduleOnMain(() -> { // Paper - push to main for event firing
- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add cause
-+ }); // Paper - push to main for event firing
- } else {
- Optional<LastSeenMessages> optional = this.tryHandleChat(packet.lastSeenMessages());
-
-@@ -2081,7 +2083,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
- @Override
- public void handleChatCommand(ServerboundChatCommandPacket packet) {
- if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.command())) {
-+ this.server.scheduleOnMain(() -> { // Paper - push to main for event firing
- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper
-+ }); // Paper - push to main for event firing
- } else {
- Optional<LastSeenMessages> optional = this.tryHandleChat(packet.lastSeenMessages());
-
-@@ -2137,7 +2141,9 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
- private void handleMessageDecodeFailure(SignedMessageChain.DecodeException exception) {
- ServerGamePacketListenerImpl.LOGGER.warn("Failed to update secure chat state for {}: '{}'", this.player.getGameProfile().getName(), exception.getComponent().getString());
- if (exception.shouldDisconnect()) {
-+ this.server.scheduleOnMain(() -> { // Paper - push to main
- this.disconnect(exception.getComponent(), exception.kickCause); // Paper - kick event causes
-+ }); // Paper - push to main
- } else {
- this.player.sendSystemMessage(exception.getComponent().copy().withStyle(ChatFormatting.RED));
- }
diff --git a/patches/removed/1.20.3/0795-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch b/patches/removed/1.20.3/0795-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch
deleted file mode 100644
index 7f78aa076d..0000000000
--- a/patches/removed/1.20.3/0795-Fix-slime-spawners-not-spawning-outside-slime-chunks.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jake Potrebic <[email protected]>
-Date: Fri, 15 Apr 2022 17:09:28 -0700
-Subject: [PATCH] Fix slime spawners not spawning outside slime chunks
-
-Fixes MC-50647 by just checking if the spawn type is a SPAWNER
-and then bypassing the spawn check logic if on slimes if it is.
-
-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 48a420b7455f872c351e04be3918808e51b192ed..b14979ab7bed34a37fceff5589ecb789bab31318 100644
---- a/src/main/java/net/minecraft/world/entity/monster/Slime.java
-+++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java
-@@ -329,6 +329,11 @@ public class Slime extends Mob implements Enemy {
-
- public static boolean checkSlimeSpawnRules(EntityType<Slime> type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) {
- if (world.getDifficulty() != Difficulty.PEACEFUL) {
-+ // Paper start - fix slime spawners; Fixes MC-50647
-+ if (spawnReason == MobSpawnType.SPAWNER) {
-+ return random.nextInt(10) == 0;
-+ }
-+ // Paper end
- // 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;
diff --git a/patches/removed/1.20.3/0827-Add-some-minimal-debug-information-to-chat-packet-er.patch b/patches/removed/1.20.3/0827-Add-some-minimal-debug-information-to-chat-packet-er.patch
deleted file mode 100644
index 7ae9ebbdd4..0000000000
--- a/patches/removed/1.20.3/0827-Add-some-minimal-debug-information-to-chat-packet-er.patch
+++ /dev/null
@@ -1,20 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Wed, 6 Jul 2022 05:52:22 +0100
-Subject: [PATCH] Add some minimal debug information to chat packet errors
-
-TODO: potentially add some kick leeway
-
-diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-index ac87735ff07ee0833727cdf8b62f443ce16a3216..c518f78af7612f59af7f02fcf2ba5ef274f9694d 100644
---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java
-@@ -2149,7 +2149,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl
-
- private Optional<LastSeenMessages> tryHandleChat(String message, Instant timestamp, LastSeenMessages.Update acknowledgment) {
- if (!this.updateChatOrder(timestamp)) {
-- ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}'", this.player.getName().getString(), message);
-+ ServerGamePacketListenerImpl.LOGGER.warn("{} sent out-of-order chat: '{}': {} > {}", this.player.getName().getString(), message, this.lastChatTimeStamp.get().getEpochSecond(), timestamp.getEpochSecond()); // Paper
- this.server.scheduleOnMain(() -> { // Paper - push to main
- this.disconnect(Component.translatable("multiplayer.disconnect.out_of_order_chat"), org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes
- }); // Paper - push to main
diff --git a/patches/removed/1.20.3/0841-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch b/patches/removed/1.20.3/0841-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch
deleted file mode 100644
index c067fd1eaf..0000000000
--- a/patches/removed/1.20.3/0841-Use-thread-safe-random-in-ServerLoginPacketListenerI.patch
+++ /dev/null
@@ -1,18 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Thu, 11 Aug 2022 14:37:33 +0100
-Subject: [PATCH] Use thread safe random in ServerLoginPacketListenerImpl
-
-
-diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-index aac84898d2563bfb45c7d0884d65be2346d2911e..1c4f272219e68373eaae93fc5ea9af7d8f3fd6f9 100644
---- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-+++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
-@@ -50,6 +50,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener,
- static final Logger LOGGER = LogUtils.getLogger();
- private static final int MAX_TICKS_BEFORE_LOGIN = 600;
- private static final Component DISCONNECT_UNEXPECTED_QUERY = Component.translatable("multiplayer.disconnect.unexpected_query_response");
-+ private static final RandomSource RANDOM = new org.bukkit.craftbukkit.util.RandomSourceWrapper(new java.util.Random()); // Paper - This is called across threads, make safe
- private final byte[] challenge;
- final MinecraftServer server;
- public final Connection connection;
diff --git a/patches/removed/1.20.3/1047-Don-t-unpack-loot-table-for-TEs-not-in-world.patch b/patches/removed/1.20.3/1047-Don-t-unpack-loot-table-for-TEs-not-in-world.patch
deleted file mode 100644
index 2c7310b079..0000000000
--- a/patches/removed/1.20.3/1047-Don-t-unpack-loot-table-for-TEs-not-in-world.patch
+++ /dev/null
@@ -1,20 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Jake Potrebic <[email protected]>
-Date: Tue, 7 Nov 2023 18:59:04 -0800
-Subject: [PATCH] Don't unpack loot table for TEs not in world
-
-Fixed by 23w44a/1.20.3. Remove it then
-
-diff --git a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
-index 081691f9710ff1115e4308f79ed49fbc38941193..3e638f12956e57548f76c7e2403ba370f7baa249 100644
---- a/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
-+++ b/src/main/java/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
-@@ -70,7 +70,7 @@ public abstract class RandomizableContainerBlockEntity extends BaseContainerBloc
- }
-
- public void unpackLootTable(@Nullable Player player) {
-- if (this.lootableData.shouldReplenish(player) && this.level.getServer() != null) { // Paper
-+ if (this.level != null && this.lootableData.shouldReplenish(player) && this.level.getServer() != null) { // Paper - don't unpack loot table if not in world
- LootTable lootTable = this.level.getServer().getLootData().getLootTable(this.lootTable);
- if (player instanceof ServerPlayer) {
- CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, this.lootTable);
diff --git a/patches/removed/1.20/0103-Fix-Old-Sign-Conversion.patch b/patches/removed/1.20/0103-Fix-Old-Sign-Conversion.patch
deleted file mode 100644
index 3fbfa6af5a..0000000000
--- a/patches/removed/1.20/0103-Fix-Old-Sign-Conversion.patch
+++ /dev/null
@@ -1,49 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Fri, 17 Jun 2016 20:50:11 -0400
-Subject: [PATCH] Fix Old Sign Conversion
-
-1) Sign loading code was trying to parse the JSON before the check for oldSign.
- That code could then skip the old sign converting code if it triggers a JSON parse exception.
-2) New Mojang Schematic system has Tile Entities in the new converted format, but missing the Bukkit.isConverted flag
- This causes Igloos and such to render broken signs. We fix this by ignoring sign conversion for Defined Structures
-
-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 63acd109a79ed752a05df3d4f1b99309297c2055..b701a1344db066b9368841f2377ee493514bf282 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
-@@ -32,6 +32,7 @@ public abstract class BlockEntity {
- private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
- public CraftPersistentDataContainer persistentDataContainer;
- // CraftBukkit end
-+ public boolean isLoadingStructure = false; // Paper
- private static final Logger LOGGER = LogUtils.getLogger();
- private final BlockEntityType<?> type;
- @Nullable
-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 15695c9da294caf8531c59f72279aaeda6ceb23b..149728fa6371b4d8b0afaae769aacac27401ea03 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
-@@ -112,7 +112,7 @@ public class SignBlockEntity extends BlockEntity implements CommandSource { // C
- s = "\"\"";
- }
-
-- if (oldSign) {
-+ if (oldSign && !this.isLoadingStructure) { // Paper - saved structures will be in the new format, but will not have isConverted
- this.messages[i] = org.bukkit.craftbukkit.util.CraftChatMessage.fromString(s)[0];
- continue;
- }
-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 9f2f1d286e887a2c47eb4ac6fdd7f41c595adcaf..2ed4453c7744c1c99210d581af8d68bced4659c6 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
-@@ -284,7 +284,9 @@ public class StructureTemplate {
- definedstructure_blockinfo.nbt.putLong("LootTableSeed", random.nextLong());
- }
-
-+ tileentity.isLoadingStructure = true; // Paper
- tileentity.load(definedstructure_blockinfo.nbt);
-+ tileentity.isLoadingStructure = false; // Paper
- }
- }
-
diff --git a/patches/removed/1.20/0342-Increase-Light-Queue-Size.patch b/patches/removed/1.20/0342-Increase-Light-Queue-Size.patch
deleted file mode 100644
index 3b3f2cd829..0000000000
--- a/patches/removed/1.20/0342-Increase-Light-Queue-Size.patch
+++ /dev/null
@@ -1,28 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Aikar <[email protected]>
-Date: Wed, 8 Apr 2020 21:24:05 -0400
-Subject: [PATCH] Increase Light Queue Size
-
-Wiz mentioned that large WorldEdit operations cause light to run on
-main thread. The queue was small, set to 5.. this bumps it to 20
-but makes it configurable per-world.
-
-The main risk of increasing this higher is during shutdown, some
-queued light updates may be lost because mojang did not flush the
-light engine on shutdown...
-
-The queue size only puts a cap on max loss, doesn't solve that problem.
-
-diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 9367b999e91d53b742fb161fd9131a2c99e35c6d..2e29d1c3e5faf970bfaf3a545ef3553f284d68ef 100644
---- a/src/main/java/net/minecraft/server/MinecraftServer.java
-+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
-@@ -791,7 +791,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
- this.executeModerately();
- // CraftBukkit end
- if (worldserver.getWorld().getKeepSpawnInMemory()) worldloadlistener.stop(); // Paper
-- chunkproviderserver.getLightEngine().setTaskPerBatch(5);
-+ chunkproviderserver.getLightEngine().setTaskPerBatch(worldserver.paperConfig().misc.lightQueueSize); // Paper - increase light queue size
- // CraftBukkit start
- // this.updateMobSpawningFlags();
- worldserver.setSpawnSettings(this.isSpawningMonsters(), this.isSpawningAnimals());
diff --git a/patches/removed/1.20/0407-Reduce-MutableInt-allocations-from-light-engine.patch b/patches/removed/1.20/0407-Reduce-MutableInt-allocations-from-light-engine.patch
deleted file mode 100644
index 1a92be0a01..0000000000
--- a/patches/removed/1.20/0407-Reduce-MutableInt-allocations-from-light-engine.patch
+++ /dev/null
@@ -1,50 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 27 Apr 2020 02:48:06 -0700
-Subject: [PATCH] Reduce MutableInt allocations from light engine
-
-We can abuse the fact light is single threaded and share an instance
-per light engine instance
-
-diff --git a/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java
-index 729c4b1763a24bac3c0764bea505555a32e54f57..37d7165dfd17da03428f8dbbbf95aa8005be289c 100644
---- a/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java
-+++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightEngine.java
-@@ -15,6 +15,7 @@ import org.apache.commons.lang3.mutable.MutableInt;
- public final class BlockLightEngine extends LayerLightEngine<BlockLightSectionStorage.BlockDataLayerStorageMap, BlockLightSectionStorage> {
- private static final Direction[] DIRECTIONS = Direction.values();
- private final BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
-+ private final MutableInt mutableInt = new MutableInt(); // Paper
-
- public BlockLightEngine(LightChunkGetter chunkProvider) {
- super(chunkProvider, LightLayer.BLOCK, new BlockLightSectionStorage(chunkProvider));
-@@ -44,7 +45,7 @@ public final class BlockLightEngine extends LayerLightEngine<BlockLightSectionSt
- if (direction == null) {
- return 15;
- } else {
-- MutableInt mutableInt = new MutableInt();
-+ //MutableInt mutableint = new MutableInt(); // Paper - share mutableint, single threaded
- BlockState blockState = this.getStateAndOpacity(targetId, mutableInt);
- if (mutableInt.getValue() >= 15) {
- return 15;
-diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java
-index 56b8f6ac53e7598da4dea2180825242222f86731..ca710a20e8b97341616463f4058b61cf4999af28 100644
---- a/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java
-+++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightEngine.java
-@@ -15,6 +15,7 @@ import org.apache.commons.lang3.mutable.MutableInt;
- public final class SkyLightEngine extends LayerLightEngine<SkyLightSectionStorage.SkyDataLayerStorageMap, SkyLightSectionStorage> {
- private static final Direction[] DIRECTIONS = Direction.values();
- private static final Direction[] HORIZONTALS = new Direction[]{Direction.NORTH, Direction.SOUTH, Direction.WEST, Direction.EAST};
-+ private final MutableInt mutableInt = new MutableInt(); // Paper
-
- public SkyLightEngine(LightChunkGetter chunkProvider) {
- super(chunkProvider, LightLayer.SKY, new SkyLightSectionStorage(chunkProvider));
-@@ -26,7 +27,7 @@ public final class SkyLightEngine extends LayerLightEngine<SkyLightSectionStorag
- if (level >= 15) {
- return level;
- } else {
-- MutableInt mutableInt = new MutableInt();
-+ //MutableInt mutableint = new MutableInt(); // Paper - share mutableint, single threaded
- BlockState blockState = this.getStateAndOpacity(targetId, mutableInt);
- if (mutableInt.getValue() >= 15) {
- return 15;
diff --git a/patches/removed/1.20/0433-Stop-copy-on-write-operations-for-updating-light-dat.patch b/patches/removed/1.20/0433-Stop-copy-on-write-operations-for-updating-light-dat.patch
deleted file mode 100644
index c13f70aec1..0000000000
--- a/patches/removed/1.20/0433-Stop-copy-on-write-operations-for-updating-light-dat.patch
+++ /dev/null
@@ -1,297 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 27 Apr 2020 04:05:38 -0700
-Subject: [PATCH] Stop copy-on-write operations for updating light data
-
-Causes huge memory allocations + gc issues
-
-diff --git a/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java
-index 573cdb0897978eef8f5fc906ed4928293f4b2ab9..314b46f0becd088d26956b45981217b128d539cb 100644
---- a/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/lighting/BlockLightSectionStorage.java
-@@ -9,7 +9,7 @@ import net.minecraft.world.level.chunk.LightChunkGetter;
-
- public class BlockLightSectionStorage extends LayerLightSectionStorage<BlockLightSectionStorage.BlockDataLayerStorageMap> {
- protected BlockLightSectionStorage(LightChunkGetter chunkProvider) {
-- super(LightLayer.BLOCK, chunkProvider, new BlockLightSectionStorage.BlockDataLayerStorageMap(new Long2ObjectOpenHashMap<>()));
-+ super(LightLayer.BLOCK, chunkProvider, new BlockLightSectionStorage.BlockDataLayerStorageMap(new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>(), false)); // Paper - avoid copying light data
- }
-
- @Override
-@@ -20,13 +20,13 @@ public class BlockLightSectionStorage extends LayerLightSectionStorage<BlockLigh
- }
-
- protected static final class BlockDataLayerStorageMap extends DataLayerStorageMap<BlockLightSectionStorage.BlockDataLayerStorageMap> {
-- public BlockDataLayerStorageMap(Long2ObjectOpenHashMap<DataLayer> arrays) {
-- super(arrays);
-+ public BlockDataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<DataLayer> long2objectopenhashmap, boolean isVisible) { // Paper - avoid copying light data
-+ super(long2objectopenhashmap, isVisible); // Paper - avoid copying light data
- }
-
- @Override
- public BlockLightSectionStorage.BlockDataLayerStorageMap copy() {
-- return new BlockLightSectionStorage.BlockDataLayerStorageMap(this.map.clone());
-+ return new BlockDataLayerStorageMap(this.data, true); // Paper - avoid copying light data
- }
- }
- }
-diff --git a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java
-index 67ff66e232592203cf8dad605ad01eabc4dded89..f357a3473682c2d37a20fb862522c67b9979402a 100644
---- a/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java
-+++ b/src/main/java/net/minecraft/world/level/lighting/DataLayerStorageMap.java
-@@ -9,10 +9,23 @@ public abstract class DataLayerStorageMap<M extends DataLayerStorageMap<M>> {
- private final long[] lastSectionKeys = new long[2];
- private final DataLayer[] lastSections = new DataLayer[2];
- private boolean cacheEnabled;
-- protected final Long2ObjectOpenHashMap<DataLayer> map;
-+ protected final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<DataLayer> data; // Paper - avoid copying light data
-+ protected final boolean isVisible; // Paper - avoid copying light data
-+ java.util.function.Function<Long, DataLayer> lookup; // Paper - faster branchless lookup
-
-- protected DataLayerStorageMap(Long2ObjectOpenHashMap<DataLayer> arrays) {
-- this.map = arrays;
-+ // Paper start - avoid copying light data
-+ protected DataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<DataLayer> data, boolean isVisible) {
-+ if (isVisible) {
-+ data.performUpdatesLockMap();
-+ }
-+ this.data = data;
-+ this.isVisible = isVisible;
-+ if (isVisible) {
-+ lookup = data::getVisibleAsync;
-+ } else {
-+ lookup = data::getUpdating;
-+ }
-+ // Paper end - avoid copying light data
- this.clearCache();
- this.cacheEnabled = true;
- }
-@@ -20,16 +33,17 @@ public abstract class DataLayerStorageMap<M extends DataLayerStorageMap<M>> {
- public abstract M copy();
-
- public void copyDataLayer(long pos) {
-- this.map.put(pos, this.map.get(pos).copy());
-+ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data
-+ this.data.queueUpdate(pos, ((DataLayer) this.data.getUpdating(pos)).copy()); // Paper - avoid copying light data
- this.clearCache();
- }
-
- public boolean hasLayer(long chunkPos) {
-- return this.map.containsKey(chunkPos);
-+ return lookup.apply(chunkPos) != null; // Paper - avoid copying light data
- }
-
- @Nullable
-- public DataLayer getLayer(long chunkPos) {
-+ public final DataLayer getLayer(long chunkPos) { // Paper - final
- if (this.cacheEnabled) {
- for(int i = 0; i < 2; ++i) {
- if (chunkPos == this.lastSectionKeys[i]) {
-@@ -38,7 +52,7 @@ public abstract class DataLayerStorageMap<M extends DataLayerStorageMap<M>> {
- }
- }
-
-- DataLayer dataLayer = this.map.get(chunkPos);
-+ DataLayer dataLayer = lookup.apply(chunkPos); // Paper - avoid copying light data
- if (dataLayer == null) {
- return null;
- } else {
-@@ -58,11 +72,13 @@ public abstract class DataLayerStorageMap<M extends DataLayerStorageMap<M>> {
-
- @Nullable
- public DataLayer removeLayer(long chunkPos) {
-- return this.map.remove(chunkPos);
-+ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data
-+ return (DataLayer) this.data.queueRemove(chunkPos); // Paper - avoid copying light data
- }
-
- public void setLayer(long pos, DataLayer data) {
-- this.map.put(pos, data);
-+ if (this.isVisible) { throw new IllegalStateException("writing to visible data"); } // Paper - avoid copying light data
-+ this.data.queueUpdate(pos, data); // Paper - avoid copying light data
- }
-
- public void clearCache() {
-diff --git a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java
-index c899674ee24167ee3abdf1588d7396df0ab4f0aa..85175b01b1623b3bc66c65805cec26eaead48265 100644
---- a/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/lighting/LayerLightSectionStorage.java
-@@ -27,7 +27,7 @@ public abstract class LayerLightSectionStorage<M extends DataLayerStorageMap<M>>
- protected final LongSet dataSectionSet = new LongOpenHashSet();
- protected final LongSet toMarkNoData = new LongOpenHashSet();
- protected final LongSet toMarkData = new LongOpenHashSet();
-- protected volatile M visibleSectionData;
-+ protected volatile M e_visible; protected final Object visibleUpdateLock = new Object(); // Paper - diff on change, should be "visible" - force compile fail on usage change
- protected final M updatingSectionData;
- protected final LongSet changedSections = new LongOpenHashSet();
- protected final LongSet sectionsAffectedByLightUpdates = new LongOpenHashSet();
-@@ -42,8 +42,8 @@ public abstract class LayerLightSectionStorage<M extends DataLayerStorageMap<M>>
- this.layer = lightType;
- this.chunkSource = chunkProvider;
- this.updatingSectionData = lightData;
-- this.visibleSectionData = lightData.copy();
-- this.visibleSectionData.disableCache();
-+ this.e_visible = lightData.copy(); // Paper - avoid copying light dat
-+ this.e_visible.disableCache(); // Paper - avoid copying light dat
- }
-
- protected boolean storingLightForSection(long sectionPos) {
-@@ -52,7 +52,15 @@ public abstract class LayerLightSectionStorage<M extends DataLayerStorageMap<M>>
-
- @Nullable
- protected DataLayer getDataLayer(long sectionPos, boolean cached) {
-- return this.getDataLayer((M)(cached ? this.updatingSectionData : this.visibleSectionData), sectionPos);
-+ // Paper start - avoid copying light data
-+ if (cached) {
-+ return this.getDataLayer(this.updatingSectionData, sectionPos);
-+ } else {
-+ synchronized (this.visibleUpdateLock) {
-+ return this.getDataLayer(this.e_visible, sectionPos);
-+ }
-+ }
-+ // Paper end - avoid copying light data
- }
-
- @Nullable
-@@ -342,9 +350,11 @@ public abstract class LayerLightSectionStorage<M extends DataLayerStorageMap<M>>
-
- protected void swapSectionMap() {
- if (!this.changedSections.isEmpty()) {
-+ synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data
- M dataLayerStorageMap = this.updatingSectionData.copy();
- dataLayerStorageMap.disableCache();
-- this.visibleSectionData = dataLayerStorageMap;
-+ this.e_visible = dataLayerStorageMap; // Paper - avoid copying light data
-+ } // Paper - avoid copying light data
- this.changedSections.clear();
- }
-
-diff --git a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java
-index 59d2af9f883541518c203302257f03dbe957aa0b..e6c857c8b4e4e65e3cf6a75ce6d844ff61acb566 100644
---- a/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java
-+++ b/src/main/java/net/minecraft/world/level/lighting/SkyLightSectionStorage.java
-@@ -21,7 +21,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
- private volatile boolean hasSourceInconsistencies;
-
- protected SkyLightSectionStorage(LightChunkGetter chunkProvider) {
-- super(LightLayer.SKY, chunkProvider, new SkyLightSectionStorage.SkyDataLayerStorageMap(new Long2ObjectOpenHashMap<>(), new Long2IntOpenHashMap(), Integer.MAX_VALUE));
-+ super(LightLayer.SKY, chunkProvider, new SkyLightSectionStorage.SkyDataLayerStorageMap(new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>(), new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int(), Integer.MAX_VALUE, false)); // Paper - avoid copying light data
- }
-
- @Override
-@@ -32,8 +32,9 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
- protected int getLightValue(long blockPos, boolean cached) {
- long l = SectionPos.blockToSection(blockPos);
- int i = SectionPos.y(l);
-- SkyLightSectionStorage.SkyDataLayerStorageMap skyDataLayerStorageMap = cached ? this.updatingSectionData : this.visibleSectionData;
-- int j = skyDataLayerStorageMap.topSections.get(SectionPos.getZeroNode(l));
-+ synchronized (this.visibleUpdateLock) { // Paper - avoid copying light data
-+ SkyLightSectionStorage.SkyDataLayerStorageMap skyDataLayerStorageMap = (SkyLightSectionStorage.SkyDataLayerStorageMap) this.e_visible; // Paper - avoid copying light data - must be after lock acquire
-+ int j = skyDataLayerStorageMap.otherData.getVisibleAsync(SectionPos.getZeroNode(l)); // Paper - avoid copying light data
- if (j != skyDataLayerStorageMap.currentLowestY && i < j) {
- DataLayer dataLayer = this.getDataLayer(skyDataLayerStorageMap, l);
- if (dataLayer == null) {
-@@ -52,6 +53,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
- } else {
- return cached && !this.lightOnInSection(l) ? 0 : 15;
- }
-+ } // Paper - avoid copying light data
- }
-
- @Override
-@@ -59,13 +61,13 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
- int i = SectionPos.y(sectionPos);
- if ((this.updatingSectionData).currentLowestY > i) {
- (this.updatingSectionData).currentLowestY = i;
-- (this.updatingSectionData).topSections.defaultReturnValue((this.updatingSectionData).currentLowestY);
-+ (this.updatingSectionData).otherData.queueDefaultReturnValue((this.updatingSectionData).currentLowestY); // Paper - avoid copying light data
- }
-
- long l = SectionPos.getZeroNode(sectionPos);
-- int j = (this.updatingSectionData).topSections.get(l);
-+ int j = (this.updatingSectionData).otherData.getUpdating(l); // Paper - avoid copying light data
- if (j < i + 1) {
-- (this.updatingSectionData).topSections.put(l, i + 1);
-+ (this.updatingSectionData).otherData.queueUpdate(l, i + 1); // Paper - avoid copying light data
- if (this.columnsWithSkySources.contains(l)) {
- this.queueAddSource(sectionPos);
- if (j > (this.updatingSectionData).currentLowestY) {
-@@ -102,19 +104,19 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
- }
-
- int i = SectionPos.y(sectionPos);
-- if ((this.updatingSectionData).topSections.get(l) == i + 1) {
-+ if ((this.updatingSectionData).otherData.getUpdating(l) == i + 1) { // Paper - avoid copying light data
- long m;
- for(m = sectionPos; !this.storingLightForSection(m) && this.hasSectionsBelow(i); m = SectionPos.offset(m, Direction.DOWN)) {
- --i;
- }
-
- if (this.storingLightForSection(m)) {
-- (this.updatingSectionData).topSections.put(l, i + 1);
-+ (this.updatingSectionData).otherData.queueUpdate(l, i + 1); // Paper - avoid copying light data
- if (bl) {
- this.queueAddSource(m);
- }
- } else {
-- (this.updatingSectionData).topSections.remove(l);
-+ (this.updatingSectionData).otherData.queueRemove(l); // Paper - avoid copying light data
- }
- }
-
-@@ -128,7 +130,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
- protected void enableLightSources(long columnPos, boolean enabled) {
- this.runAllUpdates();
- if (enabled && this.columnsWithSkySources.add(columnPos)) {
-- int i = (this.updatingSectionData).topSections.get(columnPos);
-+ int i = (this.updatingSectionData).otherData.getUpdating(columnPos); // Paper - avoid copying light data
- if (i != (this.updatingSectionData).currentLowestY) {
- long l = SectionPos.asLong(SectionPos.x(columnPos), i - 1, SectionPos.z(columnPos));
- this.queueAddSource(l);
-@@ -152,7 +154,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
- return dataLayer;
- } else {
- long l = SectionPos.offset(sectionPos, Direction.UP);
-- int i = (this.updatingSectionData).topSections.get(SectionPos.getZeroNode(sectionPos));
-+ int i = (this.updatingSectionData).otherData.getUpdating(SectionPos.getZeroNode(sectionPos)); // Paper - avoid copying light data
- if (i != (this.updatingSectionData).currentLowestY && SectionPos.y(l) < i) {
- DataLayer dataLayer2;
- while((dataLayer2 = this.getDataLayer(l, true)) == null) {
-@@ -275,7 +277,7 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
-
- protected boolean isAboveData(long sectionPos) {
- long l = SectionPos.getZeroNode(sectionPos);
-- int i = (this.updatingSectionData).topSections.get(l);
-+ int i = (this.updatingSectionData).otherData.getUpdating(l); // Paper - avoid copying light data
- return i == (this.updatingSectionData).currentLowestY || SectionPos.y(sectionPos) >= i;
- }
-
-@@ -286,18 +288,21 @@ public class SkyLightSectionStorage extends LayerLightSectionStorage<SkyLightSec
-
- protected static final class SkyDataLayerStorageMap extends DataLayerStorageMap<SkyLightSectionStorage.SkyDataLayerStorageMap> {
- int currentLowestY;
-- final Long2IntOpenHashMap topSections;
--
-- public SkyDataLayerStorageMap(Long2ObjectOpenHashMap<DataLayer> arrays, Long2IntOpenHashMap columnToTopSection, int minSectionY) {
-- super(arrays);
-- this.topSections = columnToTopSection;
-- columnToTopSection.defaultReturnValue(minSectionY);
-+ private final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int otherData; // Paper - avoid copying light data
-+
-+ // Paper start - avoid copying light data
-+ public SkyDataLayerStorageMap(com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<DataLayer> arrays, com.destroystokyo.paper.util.map.QueuedChangesMapLong2Int columnToTopSection, int minSectionY, boolean isVisible) {
-+ super(arrays, isVisible);
-+ this.otherData = columnToTopSection;
-+ otherData.queueDefaultReturnValue(minSectionY);
-+ // Paper end
- this.currentLowestY = minSectionY;
- }
-
- @Override
- public SkyLightSectionStorage.SkyDataLayerStorageMap copy() {
-- return new SkyLightSectionStorage.SkyDataLayerStorageMap(this.map.clone(), this.topSections.clone(), this.currentLowestY);
-+ this.otherData.performUpdatesLockMap(); // Paper - avoid copying light data
-+ return new SkyLightSectionStorage.SkyDataLayerStorageMap(this.data, this.otherData, this.currentLowestY, true); // Paper - avoid copying light data
- }
- }
- }
diff --git a/patches/removed/1.20/0571-copy-TESign-isEditable-from-snapshots.patch b/patches/removed/1.20/0571-copy-TESign-isEditable-from-snapshots.patch
deleted file mode 100644
index 2eb5d9a101..0000000000
--- a/patches/removed/1.20/0571-copy-TESign-isEditable-from-snapshots.patch
+++ /dev/null
@@ -1,20 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Shane Freeder <[email protected]>
-Date: Tue, 23 Mar 2021 06:43:30 +0000
-Subject: [PATCH] copy TESign#isEditable from snapshots
-
-Dropped in 1.20 as isEditable no longer exists and the full uuid of the editing player
-is stored. New API is needed, but the current #setEditable only mutates the is_waxed state of a sign, which
-is part of the compound tag and hence already copied by applyTo.
-diff --git a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java
-index 97028a14830384f06f4f1de36abfbc6bc1b90a19..a7d75d33367933fdec27538cde5a53cd41f3c252 100644
---- a/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java
-+++ b/src/main/java/org/bukkit/craftbukkit/block/CraftSign.java
-@@ -108,6 +108,7 @@ public class CraftSign<T extends SignBlockEntity> extends CraftBlockEntityState<
- this.back.applyLegacyStringToSignSide();
-
- super.applyTo(sign);
-+ sign.isEditable = getSnapshot().isEditable; // Paper - copy manually
- }
-
- public static void openSign(Sign sign, org.bukkit.entity.HumanEntity player) { // Paper - change move open sign to HumanEntity
diff --git a/patches/removed/1.20/0746-Always-parse-protochunk-light-sources-unless-it-is-m.patch b/patches/removed/1.20/0746-Always-parse-protochunk-light-sources-unless-it-is-m.patch
deleted file mode 100644
index d994a31849..0000000000
--- a/patches/removed/1.20/0746-Always-parse-protochunk-light-sources-unless-it-is-m.patch
+++ /dev/null
@@ -1,55 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Spottedleaf <[email protected]>
-Date: Mon, 23 Aug 2021 04:50:05 -0700
-Subject: [PATCH] Always parse protochunk light sources unless it is marked as
- non-lit
-
-Chunks not marked as lit will always go through the light engine,
-so they should always have their block sources parsed.
-
-Protochunks no longer serialize light sources like this.
-
-diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-index 34a1976699571608ae19e20dc1b6020759dad909..0ec80b83a99bfdb1f985045d98a81905a8a5a3ac 100644
---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java
-@@ -327,16 +327,33 @@ public class ChunkSerializer {
- BelowZeroRetrogen belowzeroretrogen = protochunk.getBelowZeroRetrogen();
- boolean flag5 = chunkstatus.isOrAfter(ChunkStatus.LIGHT) || belowzeroretrogen != null && belowzeroretrogen.targetStatus().isOrAfter(ChunkStatus.LIGHT);
-
-- if (!flag && flag5) {
-- Iterator iterator = BlockPos.betweenClosed(chunkPos.getMinBlockX(), world.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), world.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ()).iterator();
-+ if (!flag) { // Paper - fix incorrect parsing of blocks that emit light - it should always parse it, unless the chunk is marked as lit
-+ // Paper start - let's make sure the implementation isn't as slow as possible
-+ int offX = chunkPos.x << 4;
-+ int offZ = chunkPos.z << 4;
-+
-+ int minChunkSection = io.papermc.paper.util.WorldUtil.getMinSection(world);
-+ int maxChunkSection = io.papermc.paper.util.WorldUtil.getMaxSection(world);
-+
-+ LevelChunkSection[] sections = achunksection;
-+ for (int sectionY = minChunkSection; sectionY <= maxChunkSection; ++sectionY) {
-+ LevelChunkSection section = sections[sectionY - minChunkSection];
-+ if (section == null || section.hasOnlyAir()) {
-+ // no sources in empty sections
-+ continue;
-+ }
-+ int offY = sectionY << 4;
-
-- while (iterator.hasNext()) {
-- BlockPos blockposition = (BlockPos) iterator.next();
-+ for (int index = 0; index < (16 * 16 * 16); ++index) {
-+ if (section.states.get(index).getLightEmission() <= 0) {
-+ continue;
-+ }
-
-- if (((ChunkAccess) object1).getBlockState(blockposition).getLightEmission() != 0) {
-- protochunk.addLight(blockposition);
-+ // index = x | (z << 4) | (y << 8)
-+ protochunk.addLight(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15)));
- }
- }
-+ // Paper end
- }
- }
-
diff --git a/patches/removed/1.20/0878-Workaround-for-client-lag-spikes-MC-162253.patch b/patches/removed/1.20/0878-Workaround-for-client-lag-spikes-MC-162253.patch
deleted file mode 100644
index 13f5686c79..0000000000
--- a/patches/removed/1.20/0878-Workaround-for-client-lag-spikes-MC-162253.patch
+++ /dev/null
@@ -1,116 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Brokkonaut <[email protected]>
-Date: Sat, 18 Dec 2021 19:43:36 +0100
-Subject: [PATCH] Workaround for client lag spikes (MC-162253)
-
-When crossing certain chunk boundaries, the client needlessly
-calculates light maps for chunk neighbours. In some specific map
-configurations, these calculations cause a 500ms+ freeze on the Client.
-
-This patch basically serves as a workaround by sending light maps
-to the client, so that it doesn't attempt to calculate them.
-This mitigates the frametime impact to a minimum (but it's still there).
-
-Original patch by: MeFisto94 <[email protected]>
-Co-authored-by: =?UTF-8?q?Dani=C3=ABl=20Goossens?= <[email protected]>
-Co-authored-by: Nassim Jahnke <[email protected]>
-
-Fixed in 1.20 with new light engine
-
-diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 95c50a36dc1e03ae8ab8ca89a96d1ea56da8d94c..fbe209a66c77c47935ad026dd3e45e682af91fd8 100644
---- a/src/main/java/net/minecraft/server/level/ChunkMap.java
-+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -1376,6 +1376,46 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- });
- }
-
-+ // Paper start - Fix MC-162253
-+ /**
-+ * Returns the light mask for the given chunk consisting of all non-empty sections that may need sending.
-+ */
-+ private BitSet lightMask(final LevelChunk chunk) {
-+ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections();
-+ final BitSet mask = new BitSet(this.lightEngine.getLightSectionCount());
-+
-+ // There are 2 more light sections than chunk sections so when iterating over
-+ // sections we have to increment the index by 1
-+ for (int i = 0; i < sections.length; i++) {
-+ if (!sections[i].hasOnlyAir()) {
-+ // Whenever a section is not empty, it can change lighting for the section itself (i + 1), the section below, and the section above
-+ mask.set(i);
-+ mask.set(i + 1);
-+ mask.set(i + 2);
-+ i++; // We can skip the already set upper section
-+ }
-+ }
-+ return mask;
-+ }
-+
-+ /**
-+ * Returns the ceiling light mask of all sections that are equal or lower to the highest non-empty section.
-+ */
-+ private BitSet ceilingLightMask(final LevelChunk chunk) {
-+ final net.minecraft.world.level.chunk.LevelChunkSection[] sections = chunk.getSections();
-+ for (int i = sections.length - 1; i >= 0; i--) {
-+ if (!sections[i].hasOnlyAir()) {
-+ // Add one to get the light section, one because blocks in the section above may change, and another because BitSet's toIndex is exclusive
-+ final int highest = i + 3;
-+ final BitSet mask = new BitSet(highest);
-+ mask.set(0, highest);
-+ return mask;
-+ }
-+ }
-+ return new BitSet();
-+ }
-+ // Paper end - Fix MC-162253
-+
- // Paper start - Anti-Xray - Bypass
- private void playerLoadedChunk(ServerPlayer player, MutableObject<java.util.Map<Object, ClientboundLevelChunkWithLightPacket>> cachedDataPackets, LevelChunk chunk) {
- if (cachedDataPackets.getValue() == null) {
-@@ -1384,6 +1424,45 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
-
- Boolean shouldModify = chunk.getLevel().chunkPacketBlockController.shouldModify(player, chunk);
- player.trackChunk(chunk.getPos(), (Packet) cachedDataPackets.getValue().computeIfAbsent(shouldModify, (s) -> {
-+ // Paper start - Fix MC-162253
-+ final int viewDistance = getEffectiveViewDistance();
-+ final int playerChunkX = player.getBlockX() >> 4;
-+ final int playerChunkZ = player.getBlockZ() >> 4;
-+
-+ // For all loaded neighbours, send sky light for empty sections above highest non-empty section (+1) of the center chunk
-+ // otherwise the client will try to calculate lighting there on its own
-+ final BitSet lightMask = lightMask(chunk);
-+ if (!lightMask.isEmpty()) {
-+ for (int x = -1; x <= 1; x++) {
-+ for (int z = -1; z <= 1; z++) {
-+ if (x == 0 && z == 0) {
-+ continue;
-+ }
-+
-+ if (!chunk.isNeighbourLoaded(x, z)) {
-+ continue;
-+ }
-+
-+ final int neighborChunkX = chunk.getPos().x + x;
-+ final int neighborChunkZ = chunk.getPos().z + z;
-+ final int distX = Math.abs(playerChunkX - neighborChunkX);
-+ final int distZ = Math.abs(playerChunkZ - neighborChunkZ);
-+ if (Math.max(distX, distZ) > viewDistance) {
-+ continue;
-+ }
-+
-+ final LevelChunk neighbor = chunk.getRelativeNeighbourIfLoaded(x, z);
-+ final BitSet updateLightMask = (BitSet) lightMask.clone();
-+ updateLightMask.andNot(ceilingLightMask(neighbor));
-+ if (updateLightMask.isEmpty()) {
-+ continue;
-+ }
-+
-+ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(new ChunkPos(neighborChunkX, neighborChunkZ), this.lightEngine, updateLightMask, null, true));
-+ }
-+ }
-+ }
-+ // Paper end - Fix MC-162253
- return new ClientboundLevelChunkWithLightPacket(chunk, this.lightEngine, (BitSet) null, (BitSet) null, true, (Boolean) s);
- }));
- // Paper end
diff --git a/patches/removed/1.20/0880-Fix-EndDragonFight-killed-statuses-should-be-false-f.patch b/patches/removed/1.20/0880-Fix-EndDragonFight-killed-statuses-should-be-false-f.patch
deleted file mode 100644
index 21107e9d43..0000000000
--- a/patches/removed/1.20/0880-Fix-EndDragonFight-killed-statuses-should-be-false-f.patch
+++ /dev/null
@@ -1,27 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: elmital <[email protected]>
-Date: Fri, 16 Sep 2022 17:44:34 +0200
-Subject: [PATCH] Fix: EndDragonFight killed statuses should be false for newly
- created worlds
-
-This was fixed in 1.20-pre1
-
-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 230de1c71b0a6d6370df2fedb337cf0e332a7596..8cf4ae35eb66e69de32295d707db6845b4b02962 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
-@@ -113,9 +113,11 @@ public class EndDragonFight {
- if (nbt.contains("ExitPortalLocation", 10)) {
- this.portalLocation = NbtUtils.readBlockPos(nbt.getCompound("ExitPortalLocation"));
- }
-- } else {
-- this.dragonKilled = true;
-- this.previouslyKilled = true;
-+ // Paper start - Killed statuses should be false for newly created worlds
-+ // } else {
-+ // this.dragonKilled = true;
-+ // this.previouslyKilled = true;
-+ // Paper end
- }
-
- if (nbt.contains("Gateways", 9)) {
diff --git a/patches/removed/1.20/0889-Set-position-before-player-sending-on-dimension-chan.patch b/patches/removed/1.20/0889-Set-position-before-player-sending-on-dimension-chan.patch
deleted file mode 100644
index 5d6de68da5..0000000000
--- a/patches/removed/1.20/0889-Set-position-before-player-sending-on-dimension-chan.patch
+++ /dev/null
@@ -1,22 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Owen1212055 <[email protected]>
-Date: Sat, 6 Aug 2022 18:10:14 -0400
-Subject: [PATCH] Set position before player sending on dimension change
-
-This causes a moment where the player entity is sent with the previous location, and the
-teleport packet which is sent shortly after is meant to correct that.
-
-Fixed in 1.19.4 (notice how addDuringPortalTeleport is now called AFTER movement is done now)
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index b30972c3ab19795e26589cd0cdd54c43414fe368..c5dc769d13fbc2a88a731d42669d0906ee306e4b 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -1176,6 +1176,7 @@ public class ServerPlayer extends Player {
-
- // CraftBukkit end
- this.setLevel(worldserver);
-+ this.moveTo(exit.getX(), exit.getY(), exit.getZ(), exit.getYaw(), exit.getPitch()); // Paper - Set the location before
- this.connection.teleport(exit); // CraftBukkit - use internal teleport without event
- this.connection.resetPosition();
- worldserver.addDuringPortalTeleport(this);
diff --git a/patches/removed/1.20/0975-Disable-allowListing-before-received-from-client.patch b/patches/removed/1.20/0975-Disable-allowListing-before-received-from-client.patch
deleted file mode 100644
index f46b1e87f5..0000000000
--- a/patches/removed/1.20/0975-Disable-allowListing-before-received-from-client.patch
+++ /dev/null
@@ -1,24 +0,0 @@
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Owen1212055 <[email protected]>
-Date: Sun, 23 Apr 2023 18:22:50 -0400
-Subject: [PATCH] Disable allowListing before received from client
-
-The client does not send the packet needed to received this information until a little later, which can cause a condition where
-despite a player having disabled listing, they are able to be seen for a brief moment. This causes the player to be listed as an Anonymous
-Player until the actual configuration value is received from the client.
-
-Fixed by mojang in 1.20
-
-diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-index c4a070d445a0d834152eb53864eb08f4f90947ca..f4526885a57b804a754ab34675649a5466db300d 100644
---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
-+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -286,7 +286,7 @@ public class ServerPlayer extends Player {
- this.recipeBook = new ServerRecipeBook();
- this.lastSectionPos = SectionPos.of(0, 0, 0);
- this.respawnDimension = Level.OVERWORLD;
-- this.allowsListing = true;
-+ this.allowsListing = false; // Paper - Set to false by default... wait for packet sent by client to populate
- this.wardenSpawnTracker = new WardenSpawnTracker(0, 0, 0);
- this.containerSynchronizer = new ContainerSynchronizer() {
- @Override
diff --git a/scripts/moveback.py b/scripts/moveback.py
index ca2a47481d..6dec9e0759 100644
--- a/scripts/moveback.py
+++ b/scripts/moveback.py
@@ -3,7 +3,7 @@ import sys
# Use inside of server patch dir
# py ../../scripts/moveback.py ''
-patch_target = 998 # TODO: Update this
+patch_target = 100 # TODO: Update this
def increment_number(filename):